rads-db 0.1.24 → 0.1.25

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
@@ -2,6 +2,7 @@
2
2
 
3
3
  const zod = require('zod');
4
4
  const _ = require('lodash');
5
+ const uuid = require('uuid');
5
6
  const pluralize = require('pluralize');
6
7
  const cosmos = require('@azure/cosmos');
7
8
  const _radsDb = require('_rads-db');
@@ -573,66 +574,173 @@ function getWhereNameOperatorPair(whereKey) {
573
574
 
574
575
  const driverConstructors = { memory, restApi, azureCosmos };
575
576
  const drivers = {};
576
- function generateMethods(schema, validators, args) {
577
+ const computedPresets = {
578
+ createdAt: ({ doc }) => {
579
+ return doc.createdAt || (/* @__PURE__ */ new Date()).toISOString();
580
+ },
581
+ updatedAt: () => {
582
+ return (/* @__PURE__ */ new Date()).toISOString();
583
+ },
584
+ autoincrement: () => {
585
+ return 42;
586
+ },
587
+ uuid: ({ oldDoc }) => {
588
+ if (!oldDoc)
589
+ return uuid.v4();
590
+ }
591
+ };
592
+ function generateMethods(schema, validators, options) {
577
593
  const db = { _schema: schema };
594
+ const opts = { computed: {}, driver: { type: "memory" }, ...options };
578
595
  for (const key in schema) {
596
+ verifyComputedPresense(schema, opts.computed);
579
597
  if (!schema[key].decorators.entity)
580
598
  continue;
581
599
  const { handle } = schema[key];
582
600
  if (!handle)
583
601
  throw new Error(`Missing handle for entity ${key}`);
584
- const driverInstance = getDriverInstance(schema, key, args?.driver || { type: "memory" });
602
+ const driverInstance = getDriverInstance(schema, key, opts.driver);
585
603
  db[handle] = {
586
- get: async (args2, ctx) => {
587
- args2 = args2 || {};
588
- const result = await driverInstance.get(args2, ctx);
589
- if (args2.include)
590
- await handleInclude(schema, key, args2.include, [result]);
604
+ get: async (args, ctx) => {
605
+ args = args || {};
606
+ const result = await driverInstance.get(args, ctx);
607
+ if (args.include)
608
+ await handleInclude(schema, key, args.include, [result]);
609
+ await handleComputed(schema, key, opts, [result]);
591
610
  return result;
592
611
  },
593
- getMany: async (args2, ctx) => {
594
- args2 = args2 || {};
595
- const result = await driverInstance.getMany(args2, ctx);
596
- if (args2.include)
597
- await handleInclude(schema, key, args2.include, result.nodes);
612
+ getMany: async (args, ctx) => {
613
+ args = args || {};
614
+ const result = await driverInstance.getMany(args, ctx);
615
+ if (args.include)
616
+ await handleInclude(schema, key, args.include, result.nodes);
617
+ await handleComputed(schema, key, opts, result.nodes);
598
618
  return result;
599
619
  },
600
- getAll: async (args2, ctx) => {
601
- args2 = args2 || {};
602
- const result = await driverInstance.getAll(args2, ctx);
603
- if (args2.include)
604
- await handleInclude(schema, key, args2.include, result);
620
+ getAll: async (args, ctx) => {
621
+ args = args || {};
622
+ const result = await driverInstance.getAll(args, ctx);
623
+ if (args.include)
624
+ await handleInclude(schema, key, args.include, result);
625
+ await handleComputed(schema, key, opts, result);
605
626
  return result;
606
627
  },
607
- put: async (item, ctx) => {
608
- if (!item?.id)
628
+ put: async (doc, ctx) => {
629
+ if (!doc?.id)
609
630
  throw new Error("Id is required");
610
- const existingItem = await driverInstance.get({ where: { id: item.id } });
611
- if (existingItem)
612
- item = merge(existingItem, item);
613
- item = validators[key](item);
614
- await driverInstance.put(item, ctx);
615
- return item;
631
+ const oldDoc = await driverInstance.get({ where: { id: doc.id } });
632
+ if (oldDoc)
633
+ doc = merge(oldDoc, doc);
634
+ doc = validators[key](doc);
635
+ await handlePrecomputed(schema, key, opts, [{ doc, oldDoc }]);
636
+ await driverInstance.put(doc, ctx);
637
+ return doc;
616
638
  },
617
- putMany: async (items, ctx) => {
618
- const ids = items.map((i) => {
639
+ putMany: async (docs, ctx) => {
640
+ const ids = docs.map((i) => {
619
641
  if (!i?.id)
620
642
  throw new Error("Id is required");
621
643
  return i.id;
622
644
  });
623
- const existingItems = await driverInstance.getAll({ where: { id_in: ids } });
624
- const existingItemsById = ___default.keyBy(existingItems, "id");
625
- const itemsToSave = items.map((item) => {
626
- item = merge(existingItemsById[item.id], item);
627
- return validators[key](item);
645
+ const oldDocs = await driverInstance.getAll({ where: { id_in: ids } });
646
+ const oldDocsById = ___default.keyBy(oldDocs, "id");
647
+ const docArgsToSave = docs.map((doc) => {
648
+ const oldDoc = oldDocsById[doc.id];
649
+ doc = merge(oldDoc, doc);
650
+ doc = validators[key](doc);
651
+ return { oldDoc, doc };
628
652
  });
629
- await driverInstance.putMany(itemsToSave, ctx);
630
- return itemsToSave;
653
+ await handlePrecomputed(schema, key, opts, docArgsToSave);
654
+ const docsToSave = docArgsToSave.map((x) => x.doc);
655
+ await driverInstance.putMany(docsToSave, ctx);
656
+ return docsToSave;
631
657
  }
632
658
  };
633
659
  }
634
660
  return db;
635
661
  }
662
+ async function handlePrecomputed(schema, key, opts, docs) {
663
+ const { precomputedFields, nestedTypeFields, fields } = schema[key];
664
+ const computed = opts.computed;
665
+ for (const fieldName of precomputedFields || []) {
666
+ let handler = computed[key]?.[fieldName];
667
+ if (!handler) {
668
+ const preset = fields?.[fieldName].decorators?.precomputed?.preset;
669
+ handler = computedPresets[preset || ""];
670
+ if (!handler)
671
+ throw new Error(`Computed handler for ${key}.${fieldName} was not found`);
672
+ }
673
+ const precomputedResults = docs.map(async ({ doc, oldDoc }) => {
674
+ doc[fieldName] = await handler({ doc, oldDoc });
675
+ });
676
+ await Promise.all(precomputedResults);
677
+ }
678
+ for (const fieldName of nestedTypeFields || []) {
679
+ const promises = docs.map(({ doc }) => {
680
+ let subDoc = doc[fieldName];
681
+ if (!subDoc)
682
+ return void 0;
683
+ if (!___default.isArray(subDoc))
684
+ subDoc = [subDoc];
685
+ return handlePrecomputed(
686
+ schema,
687
+ fields[fieldName].type,
688
+ opts,
689
+ subDoc.map((doc2) => ({ doc: doc2 }))
690
+ );
691
+ });
692
+ await Promise.all(promises);
693
+ }
694
+ }
695
+ async function handleComputed(schema, key, opts, data) {
696
+ const { computedFields, nestedTypeFields, fields } = schema[key];
697
+ const computed = opts.computed;
698
+ for (const fieldName of computedFields || []) {
699
+ let handler = computed[key][fieldName];
700
+ if (!handler) {
701
+ const preset = fields?.[fieldName].decorators?.computed?.preset;
702
+ handler = computedPresets[preset || ""];
703
+ if (!handler)
704
+ throw new Error(`Computed handler for ${key}.${fieldName} was not found`);
705
+ }
706
+ const computedResults = data.map(async (doc) => {
707
+ doc[fieldName] = await handler({ doc });
708
+ });
709
+ await Promise.all(computedResults);
710
+ }
711
+ for (const fieldName of nestedTypeFields || []) {
712
+ const promises = data.map((doc) => {
713
+ let subDoc = doc[fieldName];
714
+ if (!subDoc)
715
+ return void 0;
716
+ if (!___default.isArray(subDoc))
717
+ subDoc = [subDoc];
718
+ return handleComputed(schema, fields[fieldName].type, opts, subDoc);
719
+ });
720
+ await Promise.all(promises);
721
+ }
722
+ }
723
+ function verifyComputedPresense(schema, computed) {
724
+ for (const typeName in schema) {
725
+ const { fields } = schema[typeName];
726
+ if (!fields)
727
+ continue;
728
+ for (const fieldName in fields) {
729
+ const c = fields[fieldName].decorators?.computed || fields[fieldName].decorators?.precomputed;
730
+ if (c) {
731
+ if (c.preset) {
732
+ if (!computedPresets[c.preset]) {
733
+ throw new Error(`Unknown preset: '${c.preset}'`);
734
+ }
735
+ } else if (!computed?.[typeName]?.[fieldName]) {
736
+ throw new Error(
737
+ `Computed definition missing for field ${typeName}.${fieldName}. You must provide it during "createRads()".`
738
+ );
739
+ }
740
+ }
741
+ }
742
+ }
743
+ }
636
744
  async function handleInclude(schema, entityName, include, result) {
637
745
  if (!result || !result.length || !include)
638
746
  return;
@@ -645,22 +753,38 @@ async function handleInclude(schema, entityName, include, result) {
645
753
  throw new Error(`Cannot find entity ${typeName}`);
646
754
  const idsToGet = /* @__PURE__ */ new Set();
647
755
  for (const item of result) {
648
- const id = item[fieldName]?.id;
649
- if (!id)
756
+ if (!item)
757
+ continue;
758
+ const fieldValue = item[fieldName];
759
+ if (!fieldValue)
650
760
  continue;
651
- idsToGet.add(id);
761
+ const fieldValues = ___default.isArray(fieldValue) ? fieldValue : [fieldValue];
762
+ for (const fv of fieldValues) {
763
+ const id = fv?.id;
764
+ if (!id)
765
+ continue;
766
+ idsToGet.add(id);
767
+ }
652
768
  }
653
769
  const driverInstance = getExistingDriverInstance(typeName);
654
770
  const relatedEntities = await driverInstance.getAll({ where: { id_in: [...idsToGet] } });
655
771
  const relatedEntitiesById = ___default.keyBy(relatedEntities, "id");
656
772
  for (const item of result) {
657
- const id = item[fieldName]?.id;
658
- if (!id)
773
+ if (!item)
659
774
  continue;
660
- const relatedEntity = relatedEntitiesById[id];
661
- if (!relatedEntity)
662
- console.warn(`Cannot find ${typeName} with id "${id}" (for ${entityName}.${fieldName} with id "${item.id}")`);
663
- item[fieldName] = relatedEntity;
775
+ const fieldValue = item[fieldName];
776
+ if (!fieldValue)
777
+ continue;
778
+ const fieldValues = ___default.isArray(fieldValue) ? fieldValue : [fieldValue];
779
+ for (const fv of fieldValues) {
780
+ const id = fv?.id;
781
+ if (!id)
782
+ continue;
783
+ const relatedEntity = relatedEntitiesById[id];
784
+ if (!relatedEntity)
785
+ console.warn(`Cannot find ${typeName} with id "${id}" (for ${entityName}.${fieldName} with id "${item.id}")`);
786
+ Object.assign(fv, relatedEntity);
787
+ }
664
788
  }
665
789
  });
666
790
  await Promise.all(downloadRelationsPromises);
@@ -703,7 +827,7 @@ function getDriverInstanceInner(schema, key, driver) {
703
827
  },
704
828
  async get(args, ctx) {
705
829
  args = { ...args, maxItemCount: 1 };
706
- const result = (await driverInstance.getMany(args, ctx))?.nodes[0] || null;
830
+ const result = (await driverInstance.getMany(args, ctx))?.nodes[0];
707
831
  return result;
708
832
  },
709
833
  async put(data, ctx) {
@@ -712,7 +836,7 @@ function getDriverInstanceInner(schema, key, driver) {
712
836
  await driverInstance.putMany([data], ctx);
713
837
  },
714
838
  async clear() {
715
- console.log("Not supported");
839
+ console.error(`"clear" not supported for driver "${driver.type}"`);
716
840
  },
717
841
  ...driverInstance
718
842
  };
@@ -726,6 +850,14 @@ function field(meta) {
726
850
  return function(a, b) {
727
851
  };
728
852
  }
853
+ function precomputed(meta) {
854
+ return function(a, b) {
855
+ };
856
+ }
857
+ function computed(meta) {
858
+ return function(a, b) {
859
+ };
860
+ }
729
861
 
730
862
  function createRads(args) {
731
863
  args = { ...args };
@@ -752,7 +884,9 @@ function getRestRoutes(db, prefix = "/") {
752
884
  return routes;
753
885
  }
754
886
 
887
+ exports.computed = computed;
755
888
  exports.createRads = createRads;
756
889
  exports.entity = entity;
757
890
  exports.field = field;
758
891
  exports.getRestRoutes = getRestRoutes;
892
+ exports.precomputed = precomputed;
package/dist/index.d.ts CHANGED
@@ -60,12 +60,18 @@ interface CreateRadsArgs {
60
60
  driver?: DriverOptions;
61
61
  drivers?: Record<string, DriverOptions>;
62
62
  defaultDriver?: string;
63
+ computed?: Record<string, Record<string, Function>>;
64
+ context?: Record<string, any>;
63
65
  }
64
66
  type Schema = Record<string, TypeDefinition>;
65
67
  type SchemaValidators = Record<string, (item: any) => any>;
66
68
  interface TypeDefinition {
67
69
  name: string;
68
70
  decorators: Record<string, Record<string, any>>;
71
+ comment?: string;
72
+ precomputedFields?: string[];
73
+ computedFields?: string[];
74
+ nestedTypeFields?: string[];
69
75
  fields?: Record<string, FieldDefinition>;
70
76
  enumValues?: string[];
71
77
  handle?: string;
@@ -127,8 +133,10 @@ interface RadsRequestContext {
127
133
 
128
134
  declare function entity(meta?: EntityDecoratorArgs): (classConstructor: Function, _ctx?: ClassDecoratorContext<any>) => void;
129
135
  declare function field(meta?: FieldDecoratorArgs): (a: any, b?: ClassFieldDecoratorContext) => void;
136
+ declare function precomputed(meta?: FieldDecoratorArgs): (a: any, b?: ClassFieldDecoratorContext) => void;
137
+ declare function computed(meta?: FieldDecoratorArgs): (a: any, b?: ClassFieldDecoratorContext) => void;
130
138
 
131
139
  declare function createRads(args?: CreateRadsArgs): RadsDb;
132
140
  declare function getRestRoutes(db: RadsDb, prefix?: string): Record<string, Record<string, Function>>;
133
141
 
134
- export { AzureCosmosDriverOptions, CreateRadsArgs, DeepPartial, Driver, DriverOptions, EntityDecoratorArgs, EntityMethods, FieldDecoratorArgs, FieldDefinition, GenerateClientNormalizedOptions, GenerateClientOptions, GetArgs, GetArgsAny, GetArgsInclude, GetManyArgs, GetManyArgsAny, GetManyResponse, GetResponse, GetResponseInclude, GetResponseIncludeSelect, GetResponseNoInclude, MemoryDriverOptions, MinimalDriver, PutArgs, RadsRequestContext, RestDriverOptions, Schema, SchemaValidators, TypeDefinition, createRads, entity, field, getRestRoutes };
142
+ export { AzureCosmosDriverOptions, CreateRadsArgs, DeepPartial, Driver, DriverOptions, EntityDecoratorArgs, EntityMethods, FieldDecoratorArgs, FieldDefinition, GenerateClientNormalizedOptions, GenerateClientOptions, GetArgs, GetArgsAny, GetArgsInclude, GetManyArgs, GetManyArgsAny, GetManyResponse, GetResponse, GetResponseInclude, GetResponseIncludeSelect, GetResponseNoInclude, MemoryDriverOptions, MinimalDriver, PutArgs, RadsRequestContext, RestDriverOptions, Schema, SchemaValidators, TypeDefinition, computed, createRads, entity, field, getRestRoutes, precomputed };
package/dist/index.mjs CHANGED
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import _ from 'lodash';
3
+ import { v4 } from 'uuid';
3
4
  import pluralize from 'pluralize';
4
5
  import { CosmosClient } from '@azure/cosmos';
5
6
  import { schema } from '_rads-db';
@@ -566,66 +567,173 @@ function getWhereNameOperatorPair(whereKey) {
566
567
 
567
568
  const driverConstructors = { memory, restApi, azureCosmos };
568
569
  const drivers = {};
569
- function generateMethods(schema, validators, args) {
570
+ const computedPresets = {
571
+ createdAt: ({ doc }) => {
572
+ return doc.createdAt || (/* @__PURE__ */ new Date()).toISOString();
573
+ },
574
+ updatedAt: () => {
575
+ return (/* @__PURE__ */ new Date()).toISOString();
576
+ },
577
+ autoincrement: () => {
578
+ return 42;
579
+ },
580
+ uuid: ({ oldDoc }) => {
581
+ if (!oldDoc)
582
+ return v4();
583
+ }
584
+ };
585
+ function generateMethods(schema, validators, options) {
570
586
  const db = { _schema: schema };
587
+ const opts = { computed: {}, driver: { type: "memory" }, ...options };
571
588
  for (const key in schema) {
589
+ verifyComputedPresense(schema, opts.computed);
572
590
  if (!schema[key].decorators.entity)
573
591
  continue;
574
592
  const { handle } = schema[key];
575
593
  if (!handle)
576
594
  throw new Error(`Missing handle for entity ${key}`);
577
- const driverInstance = getDriverInstance(schema, key, args?.driver || { type: "memory" });
595
+ const driverInstance = getDriverInstance(schema, key, opts.driver);
578
596
  db[handle] = {
579
- get: async (args2, ctx) => {
580
- args2 = args2 || {};
581
- const result = await driverInstance.get(args2, ctx);
582
- if (args2.include)
583
- await handleInclude(schema, key, args2.include, [result]);
597
+ get: async (args, ctx) => {
598
+ args = args || {};
599
+ const result = await driverInstance.get(args, ctx);
600
+ if (args.include)
601
+ await handleInclude(schema, key, args.include, [result]);
602
+ await handleComputed(schema, key, opts, [result]);
584
603
  return result;
585
604
  },
586
- getMany: async (args2, ctx) => {
587
- args2 = args2 || {};
588
- const result = await driverInstance.getMany(args2, ctx);
589
- if (args2.include)
590
- await handleInclude(schema, key, args2.include, result.nodes);
605
+ getMany: async (args, ctx) => {
606
+ args = args || {};
607
+ const result = await driverInstance.getMany(args, ctx);
608
+ if (args.include)
609
+ await handleInclude(schema, key, args.include, result.nodes);
610
+ await handleComputed(schema, key, opts, result.nodes);
591
611
  return result;
592
612
  },
593
- getAll: async (args2, ctx) => {
594
- args2 = args2 || {};
595
- const result = await driverInstance.getAll(args2, ctx);
596
- if (args2.include)
597
- await handleInclude(schema, key, args2.include, result);
613
+ getAll: async (args, ctx) => {
614
+ args = args || {};
615
+ const result = await driverInstance.getAll(args, ctx);
616
+ if (args.include)
617
+ await handleInclude(schema, key, args.include, result);
618
+ await handleComputed(schema, key, opts, result);
598
619
  return result;
599
620
  },
600
- put: async (item, ctx) => {
601
- if (!item?.id)
621
+ put: async (doc, ctx) => {
622
+ if (!doc?.id)
602
623
  throw new Error("Id is required");
603
- const existingItem = await driverInstance.get({ where: { id: item.id } });
604
- if (existingItem)
605
- item = merge(existingItem, item);
606
- item = validators[key](item);
607
- await driverInstance.put(item, ctx);
608
- return item;
624
+ const oldDoc = await driverInstance.get({ where: { id: doc.id } });
625
+ if (oldDoc)
626
+ doc = merge(oldDoc, doc);
627
+ doc = validators[key](doc);
628
+ await handlePrecomputed(schema, key, opts, [{ doc, oldDoc }]);
629
+ await driverInstance.put(doc, ctx);
630
+ return doc;
609
631
  },
610
- putMany: async (items, ctx) => {
611
- const ids = items.map((i) => {
632
+ putMany: async (docs, ctx) => {
633
+ const ids = docs.map((i) => {
612
634
  if (!i?.id)
613
635
  throw new Error("Id is required");
614
636
  return i.id;
615
637
  });
616
- const existingItems = await driverInstance.getAll({ where: { id_in: ids } });
617
- const existingItemsById = _.keyBy(existingItems, "id");
618
- const itemsToSave = items.map((item) => {
619
- item = merge(existingItemsById[item.id], item);
620
- return validators[key](item);
638
+ const oldDocs = await driverInstance.getAll({ where: { id_in: ids } });
639
+ const oldDocsById = _.keyBy(oldDocs, "id");
640
+ const docArgsToSave = docs.map((doc) => {
641
+ const oldDoc = oldDocsById[doc.id];
642
+ doc = merge(oldDoc, doc);
643
+ doc = validators[key](doc);
644
+ return { oldDoc, doc };
621
645
  });
622
- await driverInstance.putMany(itemsToSave, ctx);
623
- return itemsToSave;
646
+ await handlePrecomputed(schema, key, opts, docArgsToSave);
647
+ const docsToSave = docArgsToSave.map((x) => x.doc);
648
+ await driverInstance.putMany(docsToSave, ctx);
649
+ return docsToSave;
624
650
  }
625
651
  };
626
652
  }
627
653
  return db;
628
654
  }
655
+ async function handlePrecomputed(schema, key, opts, docs) {
656
+ const { precomputedFields, nestedTypeFields, fields } = schema[key];
657
+ const computed = opts.computed;
658
+ for (const fieldName of precomputedFields || []) {
659
+ let handler = computed[key]?.[fieldName];
660
+ if (!handler) {
661
+ const preset = fields?.[fieldName].decorators?.precomputed?.preset;
662
+ handler = computedPresets[preset || ""];
663
+ if (!handler)
664
+ throw new Error(`Computed handler for ${key}.${fieldName} was not found`);
665
+ }
666
+ const precomputedResults = docs.map(async ({ doc, oldDoc }) => {
667
+ doc[fieldName] = await handler({ doc, oldDoc });
668
+ });
669
+ await Promise.all(precomputedResults);
670
+ }
671
+ for (const fieldName of nestedTypeFields || []) {
672
+ const promises = docs.map(({ doc }) => {
673
+ let subDoc = doc[fieldName];
674
+ if (!subDoc)
675
+ return void 0;
676
+ if (!_.isArray(subDoc))
677
+ subDoc = [subDoc];
678
+ return handlePrecomputed(
679
+ schema,
680
+ fields[fieldName].type,
681
+ opts,
682
+ subDoc.map((doc2) => ({ doc: doc2 }))
683
+ );
684
+ });
685
+ await Promise.all(promises);
686
+ }
687
+ }
688
+ async function handleComputed(schema, key, opts, data) {
689
+ const { computedFields, nestedTypeFields, fields } = schema[key];
690
+ const computed = opts.computed;
691
+ for (const fieldName of computedFields || []) {
692
+ let handler = computed[key][fieldName];
693
+ if (!handler) {
694
+ const preset = fields?.[fieldName].decorators?.computed?.preset;
695
+ handler = computedPresets[preset || ""];
696
+ if (!handler)
697
+ throw new Error(`Computed handler for ${key}.${fieldName} was not found`);
698
+ }
699
+ const computedResults = data.map(async (doc) => {
700
+ doc[fieldName] = await handler({ doc });
701
+ });
702
+ await Promise.all(computedResults);
703
+ }
704
+ for (const fieldName of nestedTypeFields || []) {
705
+ const promises = data.map((doc) => {
706
+ let subDoc = doc[fieldName];
707
+ if (!subDoc)
708
+ return void 0;
709
+ if (!_.isArray(subDoc))
710
+ subDoc = [subDoc];
711
+ return handleComputed(schema, fields[fieldName].type, opts, subDoc);
712
+ });
713
+ await Promise.all(promises);
714
+ }
715
+ }
716
+ function verifyComputedPresense(schema, computed) {
717
+ for (const typeName in schema) {
718
+ const { fields } = schema[typeName];
719
+ if (!fields)
720
+ continue;
721
+ for (const fieldName in fields) {
722
+ const c = fields[fieldName].decorators?.computed || fields[fieldName].decorators?.precomputed;
723
+ if (c) {
724
+ if (c.preset) {
725
+ if (!computedPresets[c.preset]) {
726
+ throw new Error(`Unknown preset: '${c.preset}'`);
727
+ }
728
+ } else if (!computed?.[typeName]?.[fieldName]) {
729
+ throw new Error(
730
+ `Computed definition missing for field ${typeName}.${fieldName}. You must provide it during "createRads()".`
731
+ );
732
+ }
733
+ }
734
+ }
735
+ }
736
+ }
629
737
  async function handleInclude(schema, entityName, include, result) {
630
738
  if (!result || !result.length || !include)
631
739
  return;
@@ -638,22 +746,38 @@ async function handleInclude(schema, entityName, include, result) {
638
746
  throw new Error(`Cannot find entity ${typeName}`);
639
747
  const idsToGet = /* @__PURE__ */ new Set();
640
748
  for (const item of result) {
641
- const id = item[fieldName]?.id;
642
- if (!id)
749
+ if (!item)
750
+ continue;
751
+ const fieldValue = item[fieldName];
752
+ if (!fieldValue)
643
753
  continue;
644
- idsToGet.add(id);
754
+ const fieldValues = _.isArray(fieldValue) ? fieldValue : [fieldValue];
755
+ for (const fv of fieldValues) {
756
+ const id = fv?.id;
757
+ if (!id)
758
+ continue;
759
+ idsToGet.add(id);
760
+ }
645
761
  }
646
762
  const driverInstance = getExistingDriverInstance(typeName);
647
763
  const relatedEntities = await driverInstance.getAll({ where: { id_in: [...idsToGet] } });
648
764
  const relatedEntitiesById = _.keyBy(relatedEntities, "id");
649
765
  for (const item of result) {
650
- const id = item[fieldName]?.id;
651
- if (!id)
766
+ if (!item)
652
767
  continue;
653
- const relatedEntity = relatedEntitiesById[id];
654
- if (!relatedEntity)
655
- console.warn(`Cannot find ${typeName} with id "${id}" (for ${entityName}.${fieldName} with id "${item.id}")`);
656
- item[fieldName] = relatedEntity;
768
+ const fieldValue = item[fieldName];
769
+ if (!fieldValue)
770
+ continue;
771
+ const fieldValues = _.isArray(fieldValue) ? fieldValue : [fieldValue];
772
+ for (const fv of fieldValues) {
773
+ const id = fv?.id;
774
+ if (!id)
775
+ continue;
776
+ const relatedEntity = relatedEntitiesById[id];
777
+ if (!relatedEntity)
778
+ console.warn(`Cannot find ${typeName} with id "${id}" (for ${entityName}.${fieldName} with id "${item.id}")`);
779
+ Object.assign(fv, relatedEntity);
780
+ }
657
781
  }
658
782
  });
659
783
  await Promise.all(downloadRelationsPromises);
@@ -696,7 +820,7 @@ function getDriverInstanceInner(schema, key, driver) {
696
820
  },
697
821
  async get(args, ctx) {
698
822
  args = { ...args, maxItemCount: 1 };
699
- const result = (await driverInstance.getMany(args, ctx))?.nodes[0] || null;
823
+ const result = (await driverInstance.getMany(args, ctx))?.nodes[0];
700
824
  return result;
701
825
  },
702
826
  async put(data, ctx) {
@@ -705,7 +829,7 @@ function getDriverInstanceInner(schema, key, driver) {
705
829
  await driverInstance.putMany([data], ctx);
706
830
  },
707
831
  async clear() {
708
- console.log("Not supported");
832
+ console.error(`"clear" not supported for driver "${driver.type}"`);
709
833
  },
710
834
  ...driverInstance
711
835
  };
@@ -719,6 +843,14 @@ function field(meta) {
719
843
  return function(a, b) {
720
844
  };
721
845
  }
846
+ function precomputed(meta) {
847
+ return function(a, b) {
848
+ };
849
+ }
850
+ function computed(meta) {
851
+ return function(a, b) {
852
+ };
853
+ }
722
854
 
723
855
  function createRads(args) {
724
856
  args = { ...args };
@@ -745,4 +877,4 @@ function getRestRoutes(db, prefix = "/") {
745
877
  return routes;
746
878
  }
747
879
 
748
- export { createRads, entity, field, getRestRoutes };
880
+ export { computed, createRads, entity, field, getRestRoutes, precomputed };
@@ -60,6 +60,17 @@ function parseSchema(typescriptFiles) {
60
60
  throw new Error(`Entity "${key}" must have an id`);
61
61
  }
62
62
  }
63
+ for (const key in result) {
64
+ const type = result[key];
65
+ const fields = type.fields;
66
+ if (!fields) continue;
67
+ const precomputedFields = _lodash.default.values(fields).filter(f => !!f.decorators?.precomputed).map(f => f.name);
68
+ const computedFields = _lodash.default.values(fields).filter(f => !!f.decorators?.computed).map(f => f.name);
69
+ const nestedTypeFields = _lodash.default.values(fields).filter(f => result[f.type] && result[f.type].fields && !result[f.type].decorators.entity).map(f => f.name);
70
+ if (precomputedFields.length) type.precomputedFields = precomputedFields;
71
+ if (computedFields.length) type.computedFields = computedFields;
72
+ if (nestedTypeFields.length) type.nestedTypeFields = nestedTypeFields;
73
+ }
63
74
  return result;
64
75
  }
65
76
  function parseType(typeDeclaration, typeName, ctx) {
@@ -70,12 +81,7 @@ function parseType(typeDeclaration, typeName, ctx) {
70
81
  if (!nameNode || nameNode.kind !== _typescript.SyntaxKind.Identifier) throw new Error("Cannot detect class name");
71
82
  const name = nameNode.text;
72
83
  const comment = typeDeclaration.jsDoc?.[0]?.comment;
73
- const decoratorNodes = modifiers?.filter(x => x.kind === _typescript.SyntaxKind.Decorator) || [];
74
- const decoratorsArray = decoratorNodes?.map(x => parseDecorator(x, ctx));
75
- const decorators = {};
76
- for (const d of decoratorsArray) {
77
- decorators[d.name] = d.args;
78
- }
84
+ const decorators = parseDecorators(modifiers, ctx);
79
85
  if (typeDeclaration.kind === _typescript.SyntaxKind.ClassDeclaration) {
80
86
  const classDeclaration = typeDeclaration;
81
87
  const {
@@ -96,7 +102,7 @@ function parseType(typeDeclaration, typeName, ctx) {
96
102
  }
97
103
  const handle = _lodash.default.lowerFirst(name);
98
104
  const handlePlural = (0, _pluralize.default)(handle);
99
- return {
105
+ const result = {
100
106
  name,
101
107
  handle,
102
108
  handlePlural,
@@ -105,10 +111,11 @@ function parseType(typeDeclaration, typeName, ctx) {
105
111
  isExtending,
106
112
  comment
107
113
  };
114
+ return result;
108
115
  }
109
116
  if (typeDeclaration.kind === _typescript.SyntaxKind.TypeAliasDeclaration) {
110
117
  const typeAliasDeclaration = typeDeclaration;
111
- if (typeAliasDeclaration.type.kind !== _typescript.SyntaxKind.UnionType) throw new Error(`Unexpected type definition - ${typeDeclaration.getText(ctx.sourceFile)}`);
118
+ if (typeAliasDeclaration.type.kind !== _typescript.SyntaxKind.UnionType) throw new Error(`Unexpected type definition - ${typeDeclaration.getText(ctx.sourceFile)}. Did you mean 'class'?`);
112
119
  const typeAliasValue = typeAliasDeclaration.type;
113
120
  const enumValues = getEnumValues(typeAliasValue, typeDeclaration, ctx);
114
121
  if (typeDeclaration) return {
@@ -120,6 +127,16 @@ function parseType(typeDeclaration, typeName, ctx) {
120
127
  }
121
128
  throw new Error(`Unexpected type kind - "${name}"`);
122
129
  }
130
+ function parseDecorators(modifiers, ctx) {
131
+ if (!modifiers) return {};
132
+ const decoratorNodes = modifiers?.filter(x => x.kind === _typescript.SyntaxKind.Decorator) || [];
133
+ const decoratorsArray = decoratorNodes?.map(x => parseDecorator(x, ctx)) || [];
134
+ const decorators = {};
135
+ for (const d of decoratorsArray) {
136
+ decorators[d.name] = d.args;
137
+ }
138
+ return decorators;
139
+ }
123
140
  function getEnumValues(node, parentNode, ctx) {
124
141
  return node.types.map(node2 => {
125
142
  if (node2.kind !== _typescript.SyntaxKind.LiteralType) {
@@ -138,11 +155,12 @@ function parseClassMember(node, parentName, ctx) {
138
155
  const defaultValue = defaultValueDescription?.value;
139
156
  const isRequired = !node.questionToken;
140
157
  const comment = node.jsDoc?.[0]?.comment;
158
+ const decorators = parseDecorators(node.modifiers, ctx);
141
159
  const {
142
160
  isArray,
143
161
  type
144
162
  } = parseFieldType(ctx, parentName, name, node, defaultValueDescription);
145
- return {
163
+ const result = {
146
164
  type,
147
165
  defaultValue,
148
166
  name,
@@ -150,6 +168,8 @@ function parseClassMember(node, parentName, ctx) {
150
168
  isArray,
151
169
  comment
152
170
  };
171
+ if (!_lodash.default.isEmpty(decorators)) result.decorators = decorators;
172
+ return result;
153
173
  }
154
174
  function parseFieldType(ctx, parentName, fieldName, node, defaultValueDescription) {
155
175
  const supportedPrimitiveTypes = ["string", "number", "boolean"];
@@ -40,6 +40,21 @@ export function parseSchema(typescriptFiles) {
40
40
  throw new Error(`Entity "${key}" must have an id`);
41
41
  }
42
42
  }
43
+ for (const key in result) {
44
+ const type = result[key];
45
+ const fields = type.fields;
46
+ if (!fields)
47
+ continue;
48
+ const precomputedFields = _.values(fields).filter((f) => !!f.decorators?.precomputed).map((f) => f.name);
49
+ const computedFields = _.values(fields).filter((f) => !!f.decorators?.computed).map((f) => f.name);
50
+ const nestedTypeFields = _.values(fields).filter((f) => result[f.type] && result[f.type].fields && !result[f.type].decorators.entity).map((f) => f.name);
51
+ if (precomputedFields.length)
52
+ type.precomputedFields = precomputedFields;
53
+ if (computedFields.length)
54
+ type.computedFields = computedFields;
55
+ if (nestedTypeFields.length)
56
+ type.nestedTypeFields = nestedTypeFields;
57
+ }
43
58
  return result;
44
59
  }
45
60
  function parseType(typeDeclaration, typeName, ctx) {
@@ -49,12 +64,7 @@ function parseType(typeDeclaration, typeName, ctx) {
49
64
  throw new Error("Cannot detect class name");
50
65
  const name = nameNode.text;
51
66
  const comment = typeDeclaration.jsDoc?.[0]?.comment;
52
- const decoratorNodes = modifiers?.filter((x) => x.kind === SyntaxKind.Decorator) || [];
53
- const decoratorsArray = decoratorNodes?.map((x) => parseDecorator(x, ctx));
54
- const decorators = {};
55
- for (const d of decoratorsArray) {
56
- decorators[d.name] = d.args;
57
- }
67
+ const decorators = parseDecorators(modifiers, ctx);
58
68
  if (typeDeclaration.kind === SyntaxKind.ClassDeclaration) {
59
69
  const classDeclaration = typeDeclaration;
60
70
  const { members, heritageClauses } = classDeclaration;
@@ -72,12 +82,21 @@ function parseType(typeDeclaration, typeName, ctx) {
72
82
  }
73
83
  const handle = _.lowerFirst(name);
74
84
  const handlePlural = pluralize(handle);
75
- return { name, handle, handlePlural, decorators, fields, isExtending, comment };
85
+ const result = {
86
+ name,
87
+ handle,
88
+ handlePlural,
89
+ decorators,
90
+ fields,
91
+ isExtending,
92
+ comment
93
+ };
94
+ return result;
76
95
  }
77
96
  if (typeDeclaration.kind === SyntaxKind.TypeAliasDeclaration) {
78
97
  const typeAliasDeclaration = typeDeclaration;
79
98
  if (typeAliasDeclaration.type.kind !== SyntaxKind.UnionType)
80
- throw new Error(`Unexpected type definition - ${typeDeclaration.getText(ctx.sourceFile)}`);
99
+ throw new Error(`Unexpected type definition - ${typeDeclaration.getText(ctx.sourceFile)}. Did you mean 'class'?`);
81
100
  const typeAliasValue = typeAliasDeclaration.type;
82
101
  const enumValues = getEnumValues(typeAliasValue, typeDeclaration, ctx);
83
102
  if (typeDeclaration)
@@ -85,6 +104,17 @@ function parseType(typeDeclaration, typeName, ctx) {
85
104
  }
86
105
  throw new Error(`Unexpected type kind - "${name}"`);
87
106
  }
107
+ function parseDecorators(modifiers, ctx) {
108
+ if (!modifiers)
109
+ return {};
110
+ const decoratorNodes = modifiers?.filter((x) => x.kind === SyntaxKind.Decorator) || [];
111
+ const decoratorsArray = decoratorNodes?.map((x) => parseDecorator(x, ctx)) || [];
112
+ const decorators = {};
113
+ for (const d of decoratorsArray) {
114
+ decorators[d.name] = d.args;
115
+ }
116
+ return decorators;
117
+ }
88
118
  function getEnumValues(node, parentNode, ctx) {
89
119
  return node.types.map((node2) => {
90
120
  if (node2.kind !== SyntaxKind.LiteralType) {
@@ -102,8 +132,12 @@ function parseClassMember(node, parentName, ctx) {
102
132
  const defaultValue = defaultValueDescription?.value;
103
133
  const isRequired = !node.questionToken;
104
134
  const comment = node.jsDoc?.[0]?.comment;
135
+ const decorators = parseDecorators(node.modifiers, ctx);
105
136
  const { isArray, type } = parseFieldType(ctx, parentName, name, node, defaultValueDescription);
106
- return { type, defaultValue, name, isRequired, isArray, comment };
137
+ const result = { type, defaultValue, name, isRequired, isArray, comment };
138
+ if (!_.isEmpty(decorators))
139
+ result.decorators = decorators;
140
+ return result;
107
141
  }
108
142
  function parseFieldType(ctx, parentName, fieldName, node, defaultValueDescription) {
109
143
  const supportedPrimitiveTypes = ["string", "number", "boolean"];
package/package.json CHANGED
@@ -28,7 +28,7 @@
28
28
  "require": "./integrations/*.cjs"
29
29
  }
30
30
  },
31
- "version": "0.1.24",
31
+ "version": "0.1.25",
32
32
  "description": "Say goodbye to boilerplate code and hello to efficient and elegant syntax.",
33
33
  "keywords": [],
34
34
  "author": "",
@@ -59,14 +59,16 @@
59
59
  "tsup": "^6.7.0",
60
60
  "unbuild": "^1.2.1",
61
61
  "vite": "^4.0.0",
62
- "vitest": "^0.31.0"
62
+ "vitest": "^0.31.0",
63
+ "@types/lodash": "4.14.191",
64
+ "@types/uuid": "^9.0.2"
63
65
  },
64
66
  "dependencies": {
65
67
  "@nuxt/kit": "^3.5.1",
66
- "@types/lodash": "4.14.191",
67
68
  "dataloader": "^2.2.2",
68
69
  "lodash": "^4.17.21",
69
70
  "pluralize": "^8.0.0",
71
+ "uuid": ">=8",
70
72
  "zod": "^3.21.4"
71
73
  },
72
74
  "scripts": {