rads-db 3.0.59 → 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 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,67 +654,96 @@ 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) {
@@ -662,7 +756,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
662
756
  if (!result2)
663
757
  result2 = await driverInstance.getMany(args, ctx);
664
758
  if (args.include)
665
- await handleInclude(computedContext, args.include, result2.nodes, ctx);
759
+ await radsDbRelations.handleInclude(computedContext, args.include, result2.nodes, ctx);
666
760
  await handleComputed(computedContext, result2.nodes, ctx);
667
761
  await afterGet(result2.nodes, args, ctx, computedContext);
668
762
  return result2;
@@ -699,7 +793,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
699
793
  }
700
794
  d.doc = validatedDoc;
701
795
  }
702
- await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
796
+ await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
703
797
  const docsToSave = docArgsToSave.map((x) => x.doc);
704
798
  await driverInstance.putMany(docsToSave, ctx);
705
799
  await handleEffectsAfterPut(computedContext, docArgsToSave, beforePutResults, ctx);
@@ -742,7 +836,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
742
836
  }
743
837
  d.doc = validatedDoc;
744
838
  }
745
- await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
839
+ await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
746
840
  for (const { oldDoc, doc } of docArgsToSave) {
747
841
  const d = diff(doc, oldDoc);
748
842
  if (___default.isEmpty(d))
@@ -801,7 +895,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
801
895
  result2 = await driverInstance.get(args, ctx);
802
896
  const resultArray = result2 ? [result2] : [];
803
897
  if (result2 && args.include)
804
- await handleInclude(computedContext, args.include, resultArray, ctx);
898
+ await radsDbRelations.handleInclude(computedContext, args.include, resultArray, ctx);
805
899
  if (result2)
806
900
  await handleComputed(computedContext, resultArray, ctx);
807
901
  await afterGet(resultArray, args, ctx, computedContext);
@@ -814,7 +908,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
814
908
  if (!result2)
815
909
  result2 = await driverInstance.getAll(args, ctx);
816
910
  if (args.include)
817
- await handleInclude(computedContext, args.include, result2, ctx);
911
+ await radsDbRelations.handleInclude(computedContext, args.include, result2, ctx);
818
912
  await handleComputed(computedContext, result2, ctx);
819
913
  await afterGet(result2, args, ctx, computedContext);
820
914
  return result2;
@@ -857,7 +951,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
857
951
  }
858
952
  }
859
953
  doc = validatedDoc;
860
- await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
954
+ await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
861
955
  await driverInstance.put(doc, ctx);
862
956
  await handleEffectsAfterPut(computedContext, docArgsToSave, beforePutResults, ctx);
863
957
  await afterPut(docArgsToSave, ctx, computedContext);
@@ -892,61 +986,6 @@ async function afterPut(items, ctx, computedContext) {
892
986
  await f.afterPut?.(items, ctx, computedContext);
893
987
  }
894
988
  }
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
989
 
951
990
  function generateMethods(schema, validators, options) {
952
991
  const drivers = {};
@@ -976,7 +1015,7 @@ function generateMethods(schema, validators, options) {
976
1015
  if (!opts.noComputed) {
977
1016
  verifyComputedPresense(schema, opts.computed, effects);
978
1017
  }
979
- verifyRelationsSetup(schema, effects);
1018
+ radsDbRelations.verifyRelationsSetup(schema, effects);
980
1019
  const computedContextGlobal = {
981
1020
  schema,
982
1021
  validators,
@@ -1144,12 +1183,14 @@ const restApi = (options) => (schema, entity) => {
1144
1183
  }
1145
1184
  return responseJson;
1146
1185
  }
1147
- const pluralEntityName = ___default.lowerFirst(pluralize__default(entity));
1186
+ const { handlePlural } = schema[entity] || {};
1187
+ if (!handlePlural)
1188
+ throw new Error(`Entity ${entity} was not found in schema`);
1148
1189
  const instance = {
1149
1190
  driverName: "restApi",
1150
1191
  async getMany(args) {
1151
1192
  args = args || {};
1152
- const url = `${opts.baseUrl}/${pluralEntityName}`;
1193
+ const url = `${opts.baseUrl}/${handlePlural}`;
1153
1194
  const fetchOptions = {
1154
1195
  method: "POST",
1155
1196
  body: JSON.stringify(args),
@@ -1159,7 +1200,7 @@ const restApi = (options) => (schema, entity) => {
1159
1200
  return await fetchInner(url, fetchOptions);
1160
1201
  },
1161
1202
  async putMany(item) {
1162
- const url = `${opts.baseUrl}/${pluralEntityName}`;
1203
+ const url = `${opts.baseUrl}/${handlePlural}`;
1163
1204
  const fetchOptions = {
1164
1205
  method: "PUT",
1165
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
@@ -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,67 +647,96 @@ 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) {
@@ -654,7 +749,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
654
749
  if (!result2)
655
750
  result2 = await driverInstance.getMany(args, ctx);
656
751
  if (args.include)
657
- await handleInclude(computedContext, args.include, result2.nodes, ctx);
752
+ await radsDbRelations.handleInclude(computedContext, args.include, result2.nodes, ctx);
658
753
  await handleComputed(computedContext, result2.nodes, ctx);
659
754
  await afterGet(result2.nodes, args, ctx, computedContext);
660
755
  return result2;
@@ -691,7 +786,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
691
786
  }
692
787
  d.doc = validatedDoc;
693
788
  }
694
- await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
789
+ await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
695
790
  const docsToSave = docArgsToSave.map((x) => x.doc);
696
791
  await driverInstance.putMany(docsToSave, ctx);
697
792
  await handleEffectsAfterPut(computedContext, docArgsToSave, beforePutResults, ctx);
@@ -734,7 +829,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
734
829
  }
735
830
  d.doc = validatedDoc;
736
831
  }
737
- await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
832
+ await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
738
833
  for (const { oldDoc, doc } of docArgsToSave) {
739
834
  const d = diff(doc, oldDoc);
740
835
  if (_.isEmpty(d))
@@ -793,7 +888,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
793
888
  result2 = await driverInstance.get(args, ctx);
794
889
  const resultArray = result2 ? [result2] : [];
795
890
  if (result2 && args.include)
796
- await handleInclude(computedContext, args.include, resultArray, ctx);
891
+ await radsDbRelations.handleInclude(computedContext, args.include, resultArray, ctx);
797
892
  if (result2)
798
893
  await handleComputed(computedContext, resultArray, ctx);
799
894
  await afterGet(resultArray, args, ctx, computedContext);
@@ -806,7 +901,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
806
901
  if (!result2)
807
902
  result2 = await driverInstance.getAll(args, ctx);
808
903
  if (args.include)
809
- await handleInclude(computedContext, args.include, result2, ctx);
904
+ await radsDbRelations.handleInclude(computedContext, args.include, result2, ctx);
810
905
  await handleComputed(computedContext, result2, ctx);
811
906
  await afterGet(result2, args, ctx, computedContext);
812
907
  return result2;
@@ -849,7 +944,7 @@ function getRadsDbMethods(key, computedContext, driverInstance) {
849
944
  }
850
945
  }
851
946
  doc = validatedDoc;
852
- await fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
947
+ await radsDbRelations.fillDenormFieldsBeforePut(computedContext, docArgsToSave, ctx);
853
948
  await driverInstance.put(doc, ctx);
854
949
  await handleEffectsAfterPut(computedContext, docArgsToSave, beforePutResults, ctx);
855
950
  await afterPut(docArgsToSave, ctx, computedContext);
@@ -884,61 +979,6 @@ async function afterPut(items, ctx, computedContext) {
884
979
  await f.afterPut?.(items, ctx, computedContext);
885
980
  }
886
981
  }
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
982
 
943
983
  function generateMethods(schema, validators, options) {
944
984
  const drivers = {};
@@ -968,7 +1008,7 @@ function generateMethods(schema, validators, options) {
968
1008
  if (!opts.noComputed) {
969
1009
  verifyComputedPresense(schema, opts.computed, effects);
970
1010
  }
971
- verifyRelationsSetup(schema, effects);
1011
+ radsDbRelations.verifyRelationsSetup(schema, effects);
972
1012
  const computedContextGlobal = {
973
1013
  schema,
974
1014
  validators,
@@ -1136,12 +1176,14 @@ const restApi = (options) => (schema, entity) => {
1136
1176
  }
1137
1177
  return responseJson;
1138
1178
  }
1139
- const pluralEntityName = _.lowerFirst(pluralize(entity));
1179
+ const { handlePlural } = schema[entity] || {};
1180
+ if (!handlePlural)
1181
+ throw new Error(`Entity ${entity} was not found in schema`);
1140
1182
  const instance = {
1141
1183
  driverName: "restApi",
1142
1184
  async getMany(args) {
1143
1185
  args = args || {};
1144
- const url = `${opts.baseUrl}/${pluralEntityName}`;
1186
+ const url = `${opts.baseUrl}/${handlePlural}`;
1145
1187
  const fetchOptions = {
1146
1188
  method: "POST",
1147
1189
  body: JSON.stringify(args),
@@ -1151,7 +1193,7 @@ const restApi = (options) => (schema, entity) => {
1151
1193
  return await fetchInner(url, fetchOptions);
1152
1194
  },
1153
1195
  async putMany(item) {
1154
- const url = `${opts.baseUrl}/${pluralEntityName}`;
1196
+ const url = `${opts.baseUrl}/${handlePlural}`;
1155
1197
  const fetchOptions = {
1156
1198
  method: "PUT",
1157
1199
  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.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",