@radatek/microserver 2.2.2 → 2.3.0

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/microserver.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * MicroServer
3
- * @version 2.2.2
3
+ * @version 2.3.0
4
4
  * @package @radatek/microserver
5
5
  * @copyright Darius Kisonas 2022
6
6
  * @license MIT
@@ -32,11 +32,7 @@ export declare class WebSocketError extends Error {
32
32
  statusCode: number;
33
33
  constructor(text?: string, code?: number);
34
34
  }
35
- export type Routes = () => {
36
- [key: string]: Array<any>;
37
- } | {
38
- [key: string]: Array<any>;
39
- };
35
+ export type Routes = () => Record<string, Array<any>> | Array<Array<any>>;
40
36
  export declare abstract class Plugin {
41
37
  name?: string;
42
38
  priority?: number;
@@ -48,8 +44,9 @@ export declare abstract class Plugin {
48
44
  interface PluginClass {
49
45
  new (router: Router, ...args: any): Plugin;
50
46
  }
47
+ export type ServerRequestBody<T = any> = T extends Model<infer U extends ModelSchema> ? ModelDocument<U> : Record<string, any>;
51
48
  /** Extended http.IncomingMessage */
52
- export declare class ServerRequest extends http.IncomingMessage {
49
+ export declare class ServerRequest<T = any> extends http.IncomingMessage {
53
50
  /** Request protocol: http or https */
54
51
  protocol: string;
55
52
  /** Request client IP */
@@ -67,13 +64,9 @@ export declare class ServerRequest extends http.IncomingMessage {
67
64
  /** Original url */
68
65
  originalUrl?: string;
69
66
  /** Query parameters */
70
- query: {
71
- [key: string]: string;
72
- };
67
+ query: Record<string, string>;
73
68
  /** Router named parameters */
74
- params: {
75
- [key: string]: string;
76
- };
69
+ params: Record<string, string>;
77
70
  /** Router named parameters list */
78
71
  paramsList: string[];
79
72
  /** Router */
@@ -83,7 +76,7 @@ export declare class ServerRequest extends http.IncomingMessage {
83
76
  /** Authenticated user info */
84
77
  user?: UserInfo;
85
78
  /** Model used for request */
86
- model?: Model;
79
+ model?: T;
87
80
  /** Authentication token id */
88
81
  tokenId?: string;
89
82
  /** Request raw body */
@@ -96,13 +89,9 @@ export declare class ServerRequest extends http.IncomingMessage {
96
89
  /** Rewrite request url */
97
90
  rewrite(url: string): void;
98
91
  /** Request body: JSON or POST parameters */
99
- get body(): {
100
- [key: string]: any;
101
- };
92
+ get body(): ServerRequestBody<T>;
102
93
  /** Alias to body */
103
- get post(): {
104
- [key: string]: any;
105
- };
94
+ get post(): ServerRequestBody<T>;
106
95
  /** Get websocket */
107
96
  get websocket(): WebSocket;
108
97
  /** get files list in request */
@@ -111,8 +100,8 @@ export declare class ServerRequest extends http.IncomingMessage {
111
100
  bodyDecode(res: ServerResponse, options: any, next: () => void): void;
112
101
  }
113
102
  /** Extends http.ServerResponse */
114
- export declare class ServerResponse extends http.ServerResponse {
115
- req: ServerRequest;
103
+ export declare class ServerResponse<T = any> extends http.ServerResponse {
104
+ req: ServerRequest<T>;
116
105
  router: Router;
117
106
  isJson: boolean;
118
107
  headersOnly: boolean;
@@ -208,11 +197,11 @@ export declare class WebSocket extends EventEmitter {
208
197
  * }
209
198
  * ```
210
199
  */
211
- export declare class Controller {
212
- protected req: ServerRequest;
213
- protected res: ServerResponse;
214
- model?: Model;
215
- constructor(req: ServerRequest, res: ServerResponse);
200
+ export declare class Controller<T extends Model<any> = any> {
201
+ protected req: ServerRequest<T>;
202
+ protected res: ServerResponse<T>;
203
+ get model(): T | undefined;
204
+ constructor(req: ServerRequest<T>, res: ServerResponse<T>);
216
205
  /** Generate routes for this controller */
217
206
  static routes(): any[];
218
207
  }
@@ -235,9 +224,7 @@ declare class Waiter {
235
224
  export declare class Router extends EventEmitter {
236
225
  server: MicroServer;
237
226
  auth?: Auth;
238
- plugins: {
239
- [key: string]: Plugin;
240
- };
227
+ plugins: Record<string, Plugin>;
241
228
  _waiter: Waiter;
242
229
  /** @param {MicroServer} server */
243
230
  constructor(server: MicroServer);
@@ -626,16 +613,14 @@ export declare class FileStore {
626
613
  /** save data */
627
614
  save(name: string, data: any): Promise<any>;
628
615
  /** load all files in directory */
629
- all(name: string, autosave?: boolean): Promise<{
630
- [key: string]: any;
631
- }>;
616
+ all(name: string, autosave?: boolean): Promise<Record<string, any>>;
632
617
  /** delete data file */
633
618
  delete(name: string): Promise<void>;
634
619
  /** Observe data object */
635
620
  observe(data: object, cb: (data: object, key: string, value: any) => void): object;
636
621
  }
637
622
  /** Model validation options */
638
- interface ModelValidateOptions {
623
+ interface ModelContextOptions {
639
624
  /** User info */
640
625
  user?: UserInfo;
641
626
  /** Request params */
@@ -651,23 +636,27 @@ interface ModelValidateOptions {
651
636
  /** is required */
652
637
  required?: boolean;
653
638
  /** projection fields */
654
- projection?: Document;
639
+ projection?: Record<string, 0 | 1 | true | false>;
655
640
  }
656
641
  /** Model field validation options */
657
- interface ModelValidateFieldOptions extends ModelValidateOptions {
642
+ interface ModelValidateFieldOptions extends ModelContextOptions {
658
643
  name: string;
659
- field: FieldDescriptionInternal;
660
- model: Model;
644
+ field: ResolvedFieldSchema;
645
+ model: Model<any>;
661
646
  }
662
647
  export interface ModelCallbackFunc {
663
648
  (options: any): any;
664
649
  }
650
+ type ModelBasicCtorType = typeof String | typeof Number | typeof Boolean | typeof Date;
651
+ type ModelBasicNamedType = 'string' | 'String' | 'number' | 'Number' | 'int' | 'Int' | 'integer' | 'Integer' | 'boolean' | 'Boolean' | 'object' | 'Object' | 'objectid' | 'ObjectId' | 'date' | 'Date' | 'any' | 'Any';
652
+ type ModelBasicType = ModelBasicNamedType | ModelBasicCtorType | Model<any>;
653
+ type ModelFieldSimpleType = ModelBasicType | [ModelBasicType] | [ModelBasicType, ...never[]];
665
654
  /** Model field description */
666
- export interface FieldDescriptionObject {
655
+ export interface ModelFieldSchema {
667
656
  /** Field type */
668
- type: string | Function | Model | Array<string | Function | Model>;
657
+ type: ModelFieldSimpleType;
669
658
  /** Is array */
670
- array?: boolean;
659
+ array?: true | false;
671
660
  /** Is required */
672
661
  required?: boolean | string | ModelCallbackFunc;
673
662
  /** Can read */
@@ -677,7 +666,7 @@ export interface FieldDescriptionObject {
677
666
  /** Default value */
678
667
  default?: number | string | ModelCallbackFunc;
679
668
  /** Validate function */
680
- validate?: (value: any, options: ModelValidateOptions) => string | number | object | null | Error | typeof Error;
669
+ validate?: (value: any, options: ModelContextOptions) => string | number | object | null | Error | typeof Error;
681
670
  /** Valid values */
682
671
  enum?: Array<string | number>;
683
672
  /** Minimum value for string and number */
@@ -687,64 +676,78 @@ export interface FieldDescriptionObject {
687
676
  /** Regex validation or 'email', 'url', 'date', 'time', 'date-time' */
688
677
  format?: string;
689
678
  }
690
- type FieldDescription = FieldDescriptionObject | string | Function | Model | FieldDescription[];
691
- interface FieldDescriptionInternal {
679
+ interface ResolvedFieldSchema {
692
680
  type: string;
693
- model?: Model;
681
+ model?: Model<any>;
694
682
  required?: ModelCallbackFunc;
695
683
  canRead: ModelCallbackFunc;
696
684
  canWrite: ModelCallbackFunc;
697
685
  default: ModelCallbackFunc;
698
686
  validate: (value: any, options: ModelValidateFieldOptions) => any;
699
687
  }
688
+ export interface ModelSchema {
689
+ [K: string]: ModelFieldSchema | ModelFieldSimpleType;
690
+ }
691
+ type ModelDocumentTypeByName<T> = T extends 'string' | 'String' ? string : T extends 'number' | 'Number' | 'int' | 'Int' ? number : T extends 'boolean' | 'Boolean' ? boolean : T extends 'date' | 'Date' ? Date : T extends 'objectid' | 'ObjectId' ? string : T extends 'object' | 'Object' ? Record<string, any> : T extends 'any' | 'Any' ? any : never;
692
+ type ModelDocumentTypeByCtor<T> = T extends typeof String ? string : T extends typeof Number ? number : T extends typeof Boolean ? boolean : T extends typeof Date ? Date : T extends typeof Object ? Record<string, any> : never;
693
+ type ModelFieldTypeExtract<T extends ModelFieldSchema> = T['type'] extends string ? ModelDocumentTypeByName<T['type']> : T['type'] extends Array<infer U> ? Array<ModelDocumentType<U>> : T['type'] extends Function ? ModelDocumentTypeByCtor<T['type']> : T['type'] extends Model<infer U> ? ModelDocument<U> : never;
694
+ type ModelDocumentType<T> = T extends string ? ModelDocumentTypeByName<T> : T extends Array<infer U> ? Array<ModelDocumentType<U>> : T extends Function ? ModelDocumentTypeByCtor<T> : T extends ModelFieldSchema ? ModelFieldTypeExtract<T> : T extends Model<infer U> ? ModelDocument<U> : never;
695
+ export interface ModelDocumentField<T extends ModelFieldSchema> {
696
+ value: T['array'] extends true ? Array<ModelFieldTypeExtract<T>> : ModelFieldTypeExtract<T>;
697
+ required?: T['required'];
698
+ canRead?: T['canRead'];
699
+ canWrite?: T['canWrite'];
700
+ default?: T['default'];
701
+ validate?: T['validate'];
702
+ }
703
+ export type ModelDocument<T extends ModelSchema> = {
704
+ [K in keyof T]: ModelDocumentType<T[K]>;
705
+ } & {
706
+ _id?: string;
707
+ };
700
708
  export declare interface ModelCollections {
701
709
  collection(name: string): Promise<MicroCollection>;
702
710
  }
703
- export declare class Model {
711
+ export declare class Model<TSchema extends ModelSchema> {
704
712
  static collections?: ModelCollections;
705
- static models: {
706
- [key: string]: Model;
707
- };
713
+ static models: Record<string, Model<any>>;
714
+ static schema(schema: ModelSchema): ModelSchema;
708
715
  /** Define model */
709
- static define(name: string, fields: {
710
- [key: string]: FieldDescription;
711
- }, options?: {
716
+ static define<T extends ModelSchema>(name: string, schema: T, options?: {
712
717
  collection?: MicroCollection | Promise<MicroCollection>;
713
718
  class?: typeof Model;
714
- }): Model;
719
+ }): Model<T>;
715
720
  /** Model fields description */
716
- model: {
717
- [key: string]: FieldDescriptionInternal;
718
- };
719
- /** Model name */
720
- name: string;
721
+ model: Record<string, ResolvedFieldSchema>;
721
722
  /** Model collection for persistance */
722
723
  collection?: MicroCollection | Promise<MicroCollection>;
724
+ /** Custom options */
725
+ options: Record<string, any>;
723
726
  /** Create model acording to description */
724
- constructor(fields: {
725
- [key: string]: FieldDescription;
726
- }, options?: {
727
+ constructor(schema: TSchema, options?: {
727
728
  collection?: MicroCollection | Promise<MicroCollection>;
728
729
  name?: string;
729
730
  });
731
+ /** Get model name */
732
+ get name(): any;
730
733
  /** Validate data over model */
731
- validate(data: Document, options?: ModelValidateOptions): Document;
734
+ document(data: Record<string, any>, options?: ModelContextOptions): ModelDocument<TSchema>;
732
735
  /** Generate filter for data queries */
733
- getFilter(data: Document, options?: ModelValidateOptions): Document;
736
+ getFilter(data: Record<string, any>, options?: ModelContextOptions): Record<string, any>;
734
737
  /** Find one document */
735
- findOne(query: Query, options?: ModelValidateOptions): Promise<Document | undefined>;
738
+ findOne(query: Query, options?: ModelContextOptions): Promise<ModelDocument<TSchema> | undefined>;
736
739
  /** Find many documents */
737
- findMany(query: Query, options?: ModelValidateOptions): Promise<Document[]>;
740
+ findMany(query: Query, options?: ModelContextOptions): Promise<ModelDocument<TSchema>[]>;
738
741
  /** Insert a new document */
739
- insert(data: Document, options?: ModelValidateOptions): Promise<void>;
742
+ insert(data: Record<string, any>, options?: ModelContextOptions): Promise<void>;
740
743
  /** Update one matching document */
741
- update(query: Query, options?: ModelValidateOptions): Promise<void>;
744
+ update(query: Record<string, any>, options?: ModelContextOptions): Promise<void>;
742
745
  /** Delete one matching document */
743
- delete(query: Query, options?: ModelValidateOptions): Promise<void>;
746
+ delete(query: Query, options?: ModelContextOptions): Promise<void>;
744
747
  /** Microserver middleware */
745
748
  handler(req: ServerRequest, res: ServerResponse): any;
746
749
  }
747
- export declare interface MicroCollectionOptions {
750
+ export declare interface MicroCollectionOptions<T extends ModelSchema = any> {
748
751
  /** Collection name */
749
752
  name?: string;
750
753
  /** Collection persistent store */
@@ -752,22 +755,17 @@ export declare interface MicroCollectionOptions {
752
755
  /** Custom data loader */
753
756
  load?: (col: MicroCollection) => Promise<object>;
754
757
  /** Custom data saver */
755
- save?: (id: string, doc: Document | undefined, col: MicroCollection) => Promise<Document>;
758
+ save?: (id: string, doc: ModelDocument<T> | undefined, col: MicroCollection) => Promise<ModelDocument<T>>;
756
759
  /** Preloaded data object */
757
- data?: {
758
- [key: string]: Document;
759
- };
760
+ data?: Record<string, ModelDocument<T>>;
760
761
  }
761
762
  export declare interface Query {
762
763
  [key: string]: any;
763
764
  }
764
- export declare interface Document {
765
- [key: string]: any;
766
- }
767
765
  /** Cursor */
768
- export declare interface Cursor {
766
+ export declare interface Cursor<T extends ModelSchema> {
769
767
  forEach(cb: Function, self?: any): Promise<number>;
770
- all(): Promise<Document[]>;
768
+ all(): Promise<ModelDocument<T>[]>;
771
769
  }
772
770
  /** Find options */
773
771
  export declare interface FindOptions {
@@ -782,38 +780,38 @@ export declare interface FindOptions {
782
780
  /** maximum number of hits */
783
781
  limit?: number;
784
782
  }
783
+ /** Collection factory */
784
+ export declare class MicroCollectionStore {
785
+ constructor(dataPath?: string);
786
+ /** Get collection */
787
+ collection(name: string): Promise<MicroCollection>;
788
+ }
785
789
  /** minimalistic indexed mongo type collection with persistance for usage with Model */
786
- export declare class MicroCollection {
790
+ export declare class MicroCollection<TSchema extends ModelSchema = any> {
787
791
  /** Collection name */
788
792
  name: string;
789
793
  /** Collection data */
790
- data: {
791
- [key: string]: Document;
792
- };
793
- /** Get collections factory */
794
- static collections(options: MicroCollectionOptions): ModelCollections;
795
- constructor(options?: MicroCollectionOptions);
796
- /** check for collection is ready */
797
- protected checkReady(): Promise<void>;
794
+ data: Record<string, ModelDocument<TSchema>>;
795
+ constructor(options?: MicroCollectionOptions<TSchema>);
798
796
  /** Query document with query filter */
799
- protected queryDocument(query?: Query, data?: Document): Document;
797
+ protected queryDocument(query?: Query, data?: ModelDocument<TSchema>): ModelDocument<TSchema>;
800
798
  /** Count all documents */
801
799
  countDocuments(): Promise<number>;
802
800
  /** Find one matching document */
803
- findOne(query: Query): Promise<Document | undefined>;
801
+ findOne(query: Query): Promise<ModelDocument<TSchema> | undefined>;
804
802
  /** Find all matching documents */
805
- find(query: Query): Cursor;
803
+ find(query: Query): Cursor<TSchema>;
806
804
  /** Find and modify one matching document */
807
805
  findAndModify(options: FindOptions): Promise<number>;
808
806
  /** Insert one document */
809
- insertOne(doc: Document): Promise<Document>;
807
+ insertOne(doc: ModelDocument<TSchema>): Promise<ModelDocument<TSchema>>;
810
808
  /** Insert multiple documents */
811
- insertMany(docs: Document[]): Promise<Document[]>;
809
+ insertMany(docs: ModelDocument<TSchema>[]): Promise<ModelDocument<TSchema>[]>;
812
810
  /** Delete one matching document */
813
811
  deleteOne(query: Query): Promise<void>;
814
812
  /** Delete all matching documents */
815
813
  deleteMany(query: Query): Promise<number>;
816
- updateOne(query: Query, doc: Document, options?: FindOptions): Promise<{
814
+ updateOne(query: Query, doc: ModelDocument<TSchema>, options?: FindOptions): Promise<{
817
815
  upsertedId: any;
818
816
  modifiedCount: number;
819
817
  }>;
package/microserver.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * MicroServer
3
- * @version 2.2.2
3
+ * @version 2.3.0
4
4
  * @package @radatek/microserver
5
5
  * @copyright Darius Kisonas 2022
6
6
  * @license MIT
@@ -789,6 +789,9 @@ const server = {};
789
789
  * ```
790
790
  */
791
791
  export class Controller {
792
+ get model() {
793
+ return this.req.model;
794
+ }
792
795
  constructor(req, res) {
793
796
  this.req = req;
794
797
  this.res = res;
@@ -810,7 +813,7 @@ export class Controller {
810
813
  let acl = thisStatic['acl:' + key] ?? thisStatic['acl'];
811
814
  const user = thisStatic['user:' + key] ?? thisStatic['user'];
812
815
  const group = thisStatic['group:' + key] ?? thisStatic['group'];
813
- const model = thisStatic['model:' + key] ?? thisStatic['model'];
816
+ const modelName = thisStatic['model:' + key] ?? thisStatic['model'];
814
817
  let method = '';
815
818
  if (!url)
816
819
  key = key.replaceAll('$', '/');
@@ -899,10 +902,18 @@ export class Controller {
899
902
  list.push((req, res) => {
900
903
  res.isJson = true;
901
904
  const obj = new this(req, res);
902
- if (model) {
903
- req.model = obj.model = model instanceof Model ? model : Model.models[model];
905
+ if (modelName) {
906
+ req.model = modelName instanceof Model ? modelName : Model.models[modelName];
904
907
  if (!obj.model)
905
- throw new InvalidData(model, 'model');
908
+ throw new InvalidData(modelName, 'model');
909
+ const modelOptions = { ...obj.model.options, user: req.user, params: req.params };
910
+ req.model = new Proxy(req.model, {
911
+ get: (target, prop) => {
912
+ if (prop === 'options')
913
+ return modelOptions;
914
+ return target[prop];
915
+ }
916
+ });
906
917
  }
907
918
  return func.apply(obj, req.paramsList);
908
919
  });
@@ -2563,26 +2574,31 @@ function newObjectId() {
2563
2574
  return (new Date().getTime() / 1000 | 0).toString(16) + globalObjectId.toString('hex');
2564
2575
  }
2565
2576
  export class Model {
2577
+ static schema(schema) {
2578
+ return schema;
2579
+ }
2566
2580
  /** Define model */
2567
- static define(name, fields, options) {
2581
+ static define(name, schema, options) {
2568
2582
  options = options || {};
2569
2583
  if (!options.collection && this.collections)
2570
2584
  options.collection = this.collections.collection(name);
2571
2585
  const inst = options?.class
2572
- ? new options.class(fields, { name, ...options })
2573
- : new Model(fields, { name, ...options });
2586
+ ? new options.class(schema, { name, ...options })
2587
+ : new Model(schema, { name, ...options });
2574
2588
  Model.models[name] = inst;
2575
2589
  return inst;
2576
2590
  }
2577
2591
  /** Create model acording to description */
2578
- constructor(fields, options) {
2592
+ constructor(schema, options) {
2593
+ /** Custom options */
2594
+ this.options = {};
2579
2595
  const model = this.model = {};
2580
- this.name = options?.name || this.__proto__.constructor.name;
2596
+ this.options.name = options?.name || this.__proto__.constructor.name;
2581
2597
  this.collection = options?.collection;
2582
2598
  this.handler = this.handler.bind(this);
2583
- for (const n in fields) {
2599
+ for (const n in schema) {
2584
2600
  const modelField = this.model[n] = { name: n };
2585
- let field = fields[n];
2601
+ let field = schema[n];
2586
2602
  let fieldType, isArray = false;
2587
2603
  if (typeof field === 'object' && !Array.isArray(field) && !(field instanceof Model))
2588
2604
  fieldType = field.type;
@@ -2600,7 +2616,7 @@ export class Model {
2600
2616
  if (fieldType instanceof Model) {
2601
2617
  modelField.model = fieldType;
2602
2618
  fieldType = 'model';
2603
- validateType = (value, options) => modelField.model?.validate(value, options);
2619
+ validateType = (value, options) => modelField.model?.document(value, options);
2604
2620
  }
2605
2621
  else {
2606
2622
  fieldType = fieldType.toString().toLowerCase();
@@ -2760,8 +2776,12 @@ export class Model {
2760
2776
  validators.push(field.validate);
2761
2777
  }
2762
2778
  }
2779
+ /** Get model name */
2780
+ get name() {
2781
+ return this.options.name;
2782
+ }
2763
2783
  /** Validate data over model */
2764
- validate(data, options) {
2784
+ document(data, options) {
2765
2785
  options = options || {};
2766
2786
  const prefix = options.name ? options.name + '.' : '';
2767
2787
  if (options.validate === false)
@@ -2769,7 +2789,7 @@ export class Model {
2769
2789
  const res = {};
2770
2790
  for (const name in this.model) {
2771
2791
  const field = this.model[name];
2772
- const paramOptions = { ...options, field, name: prefix + name, model: this };
2792
+ const paramOptions = { ...this.options, ...options, field, name: prefix + name, model: this };
2773
2793
  const canWrite = field.canWrite(paramOptions), canRead = field.canRead(paramOptions), required = field.required?.(paramOptions);
2774
2794
  if (options.readOnly) {
2775
2795
  if (canRead === false || !field.type)
@@ -2837,7 +2857,7 @@ export class Model {
2837
2857
  for (const name in this.model) {
2838
2858
  if (!(name in res)) {
2839
2859
  const field = this.model[name];
2840
- const paramOptions = { ...options, field, name, model: this };
2860
+ const paramOptions = { ...this.options, ...options, field, name, model: this };
2841
2861
  if ((!options?.required && name in data) || (field.required && field.default)) {
2842
2862
  if (typeof field.required === 'function' && field.required(paramOptions) && field.default && (!(name in data) || field.canWrite(options) === false))
2843
2863
  res[name] = options?.default !== false ? field.default.length ? field.default(paramOptions) : field.default() : data[name];
@@ -2859,8 +2879,9 @@ export class Model {
2859
2879
  this.collection = await this.collection;
2860
2880
  if (!this.collection)
2861
2881
  throw new AccessDenied('Database not configured');
2862
- const doc = await this.collection.findOne(this.getFilter(query, { readOnly: true, ...options }));
2863
- return doc ? this.validate(doc, { readOnly: true }) : undefined;
2882
+ options = { readOnly: true, ...this.options, ...options };
2883
+ const doc = await this.collection.findOne(this.getFilter(query, options));
2884
+ return doc ? this.document(doc, options) : undefined;
2864
2885
  }
2865
2886
  /** Find many documents */
2866
2887
  async findMany(query, options) {
@@ -2869,7 +2890,8 @@ export class Model {
2869
2890
  if (!this.collection)
2870
2891
  throw new AccessDenied('Database not configured');
2871
2892
  const res = [];
2872
- await this.collection.find(this.getFilter(query || {}, options)).forEach((doc) => res.push(this.validate(doc, { readOnly: true })));
2893
+ options = { readOnly: true, ...this.options, ...options };
2894
+ await this.collection.find(this.getFilter(query || {}, options)).forEach((doc) => res.push(this.document(doc, options)));
2873
2895
  return res;
2874
2896
  }
2875
2897
  /** Insert a new document */
@@ -2882,8 +2904,9 @@ export class Model {
2882
2904
  this.collection = await this.collection;
2883
2905
  if (!this.collection)
2884
2906
  throw new AccessDenied('Database not configured');
2907
+ options = { ...this.options, ...options };
2885
2908
  if (options?.validate !== false)
2886
- query = this.validate(query, options);
2909
+ query = this.document(query, options);
2887
2910
  const unset = query.$unset || {};
2888
2911
  for (const n in query) {
2889
2912
  if (query[n] === undefined || query[n] === null) {
@@ -2901,7 +2924,7 @@ export class Model {
2901
2924
  if (!this.collection)
2902
2925
  throw new AccessDenied('Database not configured');
2903
2926
  if (query._id)
2904
- await this.collection.deleteOne(this.getFilter(query, options));
2927
+ await this.collection.deleteOne(this.getFilter(query, { ...this.options, ...options }));
2905
2928
  }
2906
2929
  /** Microserver middleware */
2907
2930
  handler(req, res) {
@@ -2939,36 +2962,27 @@ export class Model {
2939
2962
  }
2940
2963
  Model.models = {};
2941
2964
  /** Collection factory */
2942
- class MicroCollections {
2943
- constructor(options) {
2944
- this.options = options;
2965
+ export class MicroCollectionStore {
2966
+ constructor(dataPath) {
2967
+ this._collections = new Map();
2968
+ if (dataPath)
2969
+ this._store = new FileStore({ dir: dataPath.replace(/^\w+:\/\//, '') });
2945
2970
  }
2946
2971
  /** Get collection */
2947
2972
  async collection(name) {
2948
- return new MicroCollection({ ...this.options, name });
2973
+ if (!this._collections.has(name)) {
2974
+ const data = await this._store?.load(name, true) || {};
2975
+ this._collections.set(name, new MicroCollection({ data, name }));
2976
+ }
2977
+ return this._collections.get(name);
2949
2978
  }
2950
2979
  }
2951
2980
  /** minimalistic indexed mongo type collection with persistance for usage with Model */
2952
2981
  export class MicroCollection {
2953
- /** Get collections factory */
2954
- static collections(options) {
2955
- return new MicroCollections(options);
2956
- }
2957
2982
  constructor(options = {}) {
2958
2983
  this.name = options.name || this.constructor.name;
2959
- const load = options.load ?? (options.store && ((col) => options.store?.load(col.name, true)));
2960
2984
  this.data = options.data || {};
2961
2985
  this._save = options.save;
2962
- this._ready = load?.(this)?.catch(() => { }).then(data => {
2963
- this.data = data || {};
2964
- });
2965
- }
2966
- /** check for collection is ready */
2967
- async checkReady() {
2968
- if (this._ready) {
2969
- await this._ready;
2970
- this._ready = undefined;
2971
- }
2972
2986
  }
2973
2987
  /** Query document with query filter */
2974
2988
  queryDocument(query, data) {
@@ -2988,12 +3002,10 @@ export class MicroCollection {
2988
3002
  }
2989
3003
  /** Count all documents */
2990
3004
  async countDocuments() {
2991
- await this.checkReady();
2992
3005
  return Object.keys(this.data).length;
2993
3006
  }
2994
3007
  /** Find one matching document */
2995
3008
  async findOne(query) {
2996
- await this.checkReady();
2997
3009
  const id = query._id;
2998
3010
  if (id)
2999
3011
  return this.queryDocument(query, this.data[id]);
@@ -3005,7 +3017,6 @@ export class MicroCollection {
3005
3017
  find(query) {
3006
3018
  return {
3007
3019
  forEach: async (cb, self) => {
3008
- await this._ready;
3009
3020
  let count = 0;
3010
3021
  for (const id in this.data)
3011
3022
  if (this.queryDocument(query, this.data[id])) {
@@ -3018,7 +3029,6 @@ export class MicroCollection {
3018
3029
  return count;
3019
3030
  },
3020
3031
  all: async () => {
3021
- await this._ready;
3022
3032
  return Object.values(this.data).filter(doc => this.queryDocument(query, doc));
3023
3033
  }
3024
3034
  };
@@ -3027,17 +3037,17 @@ export class MicroCollection {
3027
3037
  async findAndModify(options) {
3028
3038
  if (!options.query)
3029
3039
  return 0;
3030
- await this.checkReady();
3031
3040
  const id = ((options.upsert || options.new) && !options.query._id) ? newObjectId() : options.query._id;
3032
3041
  if (!id) {
3033
3042
  let count = 0;
3043
+ let promise = Promise.resolve();
3034
3044
  this.find(options.query).forEach((doc) => {
3035
3045
  if (this.queryDocument(options.query, doc)) {
3036
3046
  Object.assign(doc, options.update);
3037
3047
  if (this._save) {
3038
- if (!this._ready)
3039
- this._ready = Promise.resolve();
3040
- this._ready = this._ready.then(async () => {
3048
+ promise = promise.then(async () => {
3049
+ if (!doc._id)
3050
+ throw new Error('Internal error: missing _id in document');
3041
3051
  this.data[doc._id] = await this._save?.(doc._id, doc, this) || this.data[doc._id];
3042
3052
  });
3043
3053
  }
@@ -3046,6 +3056,7 @@ export class MicroCollection {
3046
3056
  return false;
3047
3057
  }
3048
3058
  });
3059
+ await promise;
3049
3060
  return count;
3050
3061
  }
3051
3062
  let doc = this.queryDocument(options.query, this.data[id]);
@@ -3075,13 +3086,11 @@ export class MicroCollection {
3075
3086
  }
3076
3087
  /** Insert one document */
3077
3088
  async insertOne(doc) {
3078
- await this.checkReady();
3079
3089
  if (doc._id && this.data[doc._id])
3080
3090
  throw new InvalidData(`Document ${doc._id} dupplicate`);
3091
+ doc = { ...doc };
3081
3092
  if (!doc._id)
3082
- doc._id = { _id: newObjectId(), ...doc };
3083
- else
3084
- doc = { ...doc };
3093
+ doc._id = newObjectId();
3085
3094
  this.data[doc._id] = doc;
3086
3095
  if (this._save)
3087
3096
  this.data[doc._id] = doc = await this._save(doc._id, doc, this) || doc;
@@ -3089,7 +3098,6 @@ export class MicroCollection {
3089
3098
  }
3090
3099
  /** Insert multiple documents */
3091
3100
  async insertMany(docs) {
3092
- await this.checkReady();
3093
3101
  docs.forEach(doc => {
3094
3102
  if (doc._id && this.data[doc._id])
3095
3103
  throw new InvalidData(`Document ${doc._id} dupplicate`);
@@ -3103,24 +3111,23 @@ export class MicroCollection {
3103
3111
  const id = query._id;
3104
3112
  if (!id)
3105
3113
  return;
3106
- await this.checkReady();
3107
3114
  delete this.data[id];
3108
3115
  }
3109
3116
  /** Delete all matching documents */
3110
3117
  async deleteMany(query) {
3111
3118
  let count = 0;
3112
- await this.checkReady();
3119
+ let promise = Promise.resolve();
3113
3120
  this.find(query).forEach((doc) => {
3114
3121
  if (this.queryDocument(query, doc)) {
3115
3122
  count++;
3123
+ if (!doc._id)
3124
+ return;
3116
3125
  delete this.data[doc._id];
3117
- if (this._save) {
3118
- if (!this._ready)
3119
- this._ready = Promise.resolve();
3120
- this._ready = this._ready.then(async () => { this._save?.(doc._id, undefined, this); });
3121
- }
3126
+ if (this._save)
3127
+ promise = promise.then(async () => { doc._id && this._save?.(doc._id, undefined, this); });
3122
3128
  }
3123
3129
  });
3130
+ await promise;
3124
3131
  return count;
3125
3132
  }
3126
3133
  async updateOne(query, doc, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@radatek/microserver",
3
- "version": "2.2.2",
3
+ "version": "2.3.0",
4
4
  "description": "HTTP MicroServer",
5
5
  "author": "Darius Kisonas",
6
6
  "license": "MIT",