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 +179 -45
- package/dist/index.d.ts +9 -1
- package/dist/index.mjs +178 -46
- package/integrations/lib.cjs +29 -9
- package/integrations/lib.mjs +43 -9
- package/package.json +5 -3
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
|
-
|
|
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,
|
|
602
|
+
const driverInstance = getDriverInstance(schema, key, opts.driver);
|
|
585
603
|
db[handle] = {
|
|
586
|
-
get: async (
|
|
587
|
-
|
|
588
|
-
const result = await driverInstance.get(
|
|
589
|
-
if (
|
|
590
|
-
await handleInclude(schema, key,
|
|
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 (
|
|
594
|
-
|
|
595
|
-
const result = await driverInstance.getMany(
|
|
596
|
-
if (
|
|
597
|
-
await handleInclude(schema, key,
|
|
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 (
|
|
601
|
-
|
|
602
|
-
const result = await driverInstance.getAll(
|
|
603
|
-
if (
|
|
604
|
-
await handleInclude(schema, key,
|
|
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 (
|
|
608
|
-
if (!
|
|
628
|
+
put: async (doc, ctx) => {
|
|
629
|
+
if (!doc?.id)
|
|
609
630
|
throw new Error("Id is required");
|
|
610
|
-
const
|
|
611
|
-
if (
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
await
|
|
615
|
-
|
|
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 (
|
|
618
|
-
const ids =
|
|
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
|
|
624
|
-
const
|
|
625
|
-
const
|
|
626
|
-
|
|
627
|
-
|
|
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
|
|
630
|
-
|
|
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
|
-
|
|
649
|
-
|
|
756
|
+
if (!item)
|
|
757
|
+
continue;
|
|
758
|
+
const fieldValue = item[fieldName];
|
|
759
|
+
if (!fieldValue)
|
|
650
760
|
continue;
|
|
651
|
-
|
|
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
|
-
|
|
658
|
-
if (!id)
|
|
773
|
+
if (!item)
|
|
659
774
|
continue;
|
|
660
|
-
const
|
|
661
|
-
if (!
|
|
662
|
-
|
|
663
|
-
|
|
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]
|
|
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.
|
|
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
|
-
|
|
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,
|
|
595
|
+
const driverInstance = getDriverInstance(schema, key, opts.driver);
|
|
578
596
|
db[handle] = {
|
|
579
|
-
get: async (
|
|
580
|
-
|
|
581
|
-
const result = await driverInstance.get(
|
|
582
|
-
if (
|
|
583
|
-
await handleInclude(schema, key,
|
|
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 (
|
|
587
|
-
|
|
588
|
-
const result = await driverInstance.getMany(
|
|
589
|
-
if (
|
|
590
|
-
await handleInclude(schema, key,
|
|
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 (
|
|
594
|
-
|
|
595
|
-
const result = await driverInstance.getAll(
|
|
596
|
-
if (
|
|
597
|
-
await handleInclude(schema, key,
|
|
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 (
|
|
601
|
-
if (!
|
|
621
|
+
put: async (doc, ctx) => {
|
|
622
|
+
if (!doc?.id)
|
|
602
623
|
throw new Error("Id is required");
|
|
603
|
-
const
|
|
604
|
-
if (
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
await
|
|
608
|
-
|
|
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 (
|
|
611
|
-
const ids =
|
|
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
|
|
617
|
-
const
|
|
618
|
-
const
|
|
619
|
-
|
|
620
|
-
|
|
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
|
|
623
|
-
|
|
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
|
-
|
|
642
|
-
|
|
749
|
+
if (!item)
|
|
750
|
+
continue;
|
|
751
|
+
const fieldValue = item[fieldName];
|
|
752
|
+
if (!fieldValue)
|
|
643
753
|
continue;
|
|
644
|
-
|
|
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
|
-
|
|
651
|
-
if (!id)
|
|
766
|
+
if (!item)
|
|
652
767
|
continue;
|
|
653
|
-
const
|
|
654
|
-
if (!
|
|
655
|
-
|
|
656
|
-
|
|
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]
|
|
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.
|
|
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 };
|
package/integrations/lib.cjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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"];
|
package/integrations/lib.mjs
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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": {
|