@radatek/microserver 2.2.2 → 2.3.1
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 +96 -102
- package/microserver.js +129 -130
- package/package.json +1 -1
package/microserver.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MicroServer
|
|
3
|
-
* @version 2.
|
|
3
|
+
* @version 2.3.1
|
|
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?:
|
|
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
|
|
215
|
-
constructor(req: ServerRequest
|
|
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
|
|
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?:
|
|
639
|
+
projection?: Record<string, 0 | 1 | true | false>;
|
|
655
640
|
}
|
|
656
641
|
/** Model field validation options */
|
|
657
|
-
interface ModelValidateFieldOptions extends
|
|
642
|
+
interface ModelValidateFieldOptions extends ModelContextOptions {
|
|
658
643
|
name: string;
|
|
659
|
-
field:
|
|
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
|
|
655
|
+
export interface ModelFieldSchema {
|
|
667
656
|
/** Field type */
|
|
668
|
-
type:
|
|
657
|
+
type: ModelFieldSimpleType;
|
|
669
658
|
/** Is array */
|
|
670
|
-
array?:
|
|
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:
|
|
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,87 +676,92 @@ export interface FieldDescriptionObject {
|
|
|
687
676
|
/** Regex validation or 'email', 'url', 'date', 'time', 'date-time' */
|
|
688
677
|
format?: string;
|
|
689
678
|
}
|
|
690
|
-
|
|
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
|
-
|
|
707
|
-
};
|
|
713
|
+
static models: Record<string, Model<any>>;
|
|
714
|
+
static schema(schema: ModelSchema): ModelSchema;
|
|
708
715
|
/** Define model */
|
|
709
|
-
static define(name: string,
|
|
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(
|
|
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
|
-
|
|
734
|
+
document(data: Record<string, any>, options?: ModelContextOptions): ModelDocument<TSchema>;
|
|
732
735
|
/** Generate filter for data queries */
|
|
733
|
-
getFilter(data:
|
|
736
|
+
getFilter(data: Record<string, any>, options?: ModelContextOptions): Record<string, any>;
|
|
734
737
|
/** Find one document */
|
|
735
|
-
findOne(query: Query, options?:
|
|
738
|
+
findOne(query: Query, options?: ModelContextOptions): Promise<ModelDocument<TSchema> | undefined>;
|
|
736
739
|
/** Find many documents */
|
|
737
|
-
findMany(query: Query, options?:
|
|
740
|
+
findMany(query: Query, options?: ModelContextOptions): Promise<ModelDocument<TSchema>[]>;
|
|
738
741
|
/** Insert a new document */
|
|
739
|
-
insert(data:
|
|
742
|
+
insert(data: Record<string, any>, options?: ModelContextOptions): Promise<ModelDocument<TSchema>>;
|
|
740
743
|
/** Update one matching document */
|
|
741
|
-
update(query:
|
|
744
|
+
update(query: Record<string, any>, options?: ModelContextOptions): Promise<ModelDocument<TSchema>>;
|
|
742
745
|
/** Delete one matching document */
|
|
743
|
-
delete(query: Query, options?:
|
|
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
|
-
/** Collection persistent store */
|
|
751
|
-
store?: FileStore;
|
|
752
|
-
/** Custom data loader */
|
|
753
|
-
load?: (col: MicroCollection) => Promise<object>;
|
|
754
753
|
/** Custom data saver */
|
|
755
|
-
save?: (id: string, doc:
|
|
754
|
+
save?: (id: string, doc: ModelDocument<T> | undefined, col: MicroCollection) => Promise<ModelDocument<T>>;
|
|
756
755
|
/** Preloaded data object */
|
|
757
|
-
data?:
|
|
758
|
-
[key: string]: Document;
|
|
759
|
-
};
|
|
756
|
+
data?: Record<string, ModelDocument<T>>;
|
|
760
757
|
}
|
|
761
758
|
export declare interface Query {
|
|
762
759
|
[key: string]: any;
|
|
763
760
|
}
|
|
764
|
-
export declare interface Document {
|
|
765
|
-
[key: string]: any;
|
|
766
|
-
}
|
|
767
761
|
/** Cursor */
|
|
768
|
-
export declare interface Cursor {
|
|
762
|
+
export declare interface Cursor<T extends ModelSchema> {
|
|
769
763
|
forEach(cb: Function, self?: any): Promise<number>;
|
|
770
|
-
all(): Promise<
|
|
764
|
+
all(): Promise<ModelDocument<T>[]>;
|
|
771
765
|
}
|
|
772
766
|
/** Find options */
|
|
773
767
|
export declare interface FindOptions {
|
|
@@ -775,49 +769,49 @@ export declare interface FindOptions {
|
|
|
775
769
|
query?: Query;
|
|
776
770
|
/** is upsert */
|
|
777
771
|
upsert?: boolean;
|
|
778
|
-
/** is
|
|
779
|
-
|
|
772
|
+
/** is upsert */
|
|
773
|
+
delete?: boolean;
|
|
780
774
|
/** update object */
|
|
781
775
|
update?: Query;
|
|
782
776
|
/** maximum number of hits */
|
|
783
777
|
limit?: number;
|
|
784
778
|
}
|
|
779
|
+
/** Collection factory */
|
|
780
|
+
export declare class MicroCollectionStore {
|
|
781
|
+
constructor(dataPath?: string);
|
|
782
|
+
/** Get collection */
|
|
783
|
+
collection(name: string): Promise<MicroCollection>;
|
|
784
|
+
}
|
|
785
785
|
/** minimalistic indexed mongo type collection with persistance for usage with Model */
|
|
786
|
-
export declare class MicroCollection {
|
|
786
|
+
export declare class MicroCollection<TSchema extends ModelSchema = any> {
|
|
787
787
|
/** Collection name */
|
|
788
788
|
name: string;
|
|
789
789
|
/** Collection data */
|
|
790
|
-
data:
|
|
791
|
-
|
|
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>;
|
|
790
|
+
data: Record<string, ModelDocument<TSchema>>;
|
|
791
|
+
constructor(options?: MicroCollectionOptions<TSchema>);
|
|
798
792
|
/** Query document with query filter */
|
|
799
|
-
protected queryDocument(query?: Query, data?:
|
|
793
|
+
protected queryDocument(query?: Query, data?: ModelDocument<TSchema>): ModelDocument<TSchema>;
|
|
800
794
|
/** Count all documents */
|
|
801
795
|
countDocuments(): Promise<number>;
|
|
802
796
|
/** Find one matching document */
|
|
803
|
-
findOne(query: Query): Promise<
|
|
797
|
+
findOne(query: Query): Promise<ModelDocument<TSchema> | undefined>;
|
|
804
798
|
/** Find all matching documents */
|
|
805
|
-
find(query: Query): Cursor
|
|
799
|
+
find(query: Query): Cursor<TSchema>;
|
|
806
800
|
/** Find and modify one matching document */
|
|
807
|
-
findAndModify(options: FindOptions): Promise<
|
|
801
|
+
findAndModify(options: FindOptions): Promise<ModelDocument<TSchema> | undefined>;
|
|
808
802
|
/** Insert one document */
|
|
809
|
-
insertOne(doc:
|
|
803
|
+
insertOne(doc: ModelDocument<TSchema>): Promise<ModelDocument<TSchema>>;
|
|
810
804
|
/** Insert multiple documents */
|
|
811
|
-
insertMany(docs:
|
|
805
|
+
insertMany(docs: ModelDocument<TSchema>[]): Promise<ModelDocument<TSchema>[]>;
|
|
812
806
|
/** Delete one matching document */
|
|
813
|
-
deleteOne(query: Query): Promise<
|
|
807
|
+
deleteOne(query: Query): Promise<number>;
|
|
814
808
|
/** Delete all matching documents */
|
|
815
809
|
deleteMany(query: Query): Promise<number>;
|
|
816
|
-
updateOne(query: Query,
|
|
810
|
+
updateOne(query: Query, update: Record<string, any>, options?: FindOptions): Promise<{
|
|
817
811
|
upsertedId: any;
|
|
818
812
|
modifiedCount: number;
|
|
819
813
|
}>;
|
|
820
|
-
updateMany(query: Query, update:
|
|
814
|
+
updateMany(query: Query, update: Record<string, any>, options?: FindOptions): Promise<{
|
|
821
815
|
upsertedId: any;
|
|
822
816
|
modifiedCount: number;
|
|
823
817
|
}>;
|
package/microserver.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MicroServer
|
|
3
|
-
* @version 2.
|
|
3
|
+
* @version 2.3.1
|
|
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
|
|
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 (
|
|
903
|
-
req.model =
|
|
905
|
+
if (modelName) {
|
|
906
|
+
req.model = modelName instanceof Model ? modelName : Model.models[modelName];
|
|
904
907
|
if (!obj.model)
|
|
905
|
-
throw new InvalidData(
|
|
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,
|
|
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(
|
|
2573
|
-
: new Model(
|
|
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(
|
|
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
|
|
2599
|
+
for (const n in schema) {
|
|
2584
2600
|
const modelField = this.model[n] = { name: n };
|
|
2585
|
-
let field =
|
|
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?.
|
|
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
|
-
|
|
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
|
-
|
|
2863
|
-
|
|
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
|
-
|
|
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.
|
|
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) {
|
|
@@ -2893,6 +2916,9 @@ export class Model {
|
|
|
2893
2916
|
}
|
|
2894
2917
|
}
|
|
2895
2918
|
const res = await this.collection.findAndModify({ query: this.getFilter(query, { required: true, validate: false, default: false }), update: query, upsert: options?.insert });
|
|
2919
|
+
if (!res)
|
|
2920
|
+
throw new NotFound('Document not found');
|
|
2921
|
+
return res;
|
|
2896
2922
|
}
|
|
2897
2923
|
/** Delete one matching document */
|
|
2898
2924
|
async delete(query, options) {
|
|
@@ -2901,7 +2927,7 @@ export class Model {
|
|
|
2901
2927
|
if (!this.collection)
|
|
2902
2928
|
throw new AccessDenied('Database not configured');
|
|
2903
2929
|
if (query._id)
|
|
2904
|
-
await this.collection.deleteOne(this.getFilter(query, options));
|
|
2930
|
+
await this.collection.deleteOne(this.getFilter(query, { ...this.options, ...options }));
|
|
2905
2931
|
}
|
|
2906
2932
|
/** Microserver middleware */
|
|
2907
2933
|
handler(req, res) {
|
|
@@ -2939,36 +2965,27 @@ export class Model {
|
|
|
2939
2965
|
}
|
|
2940
2966
|
Model.models = {};
|
|
2941
2967
|
/** Collection factory */
|
|
2942
|
-
class
|
|
2943
|
-
constructor(
|
|
2944
|
-
this.
|
|
2968
|
+
export class MicroCollectionStore {
|
|
2969
|
+
constructor(dataPath) {
|
|
2970
|
+
this._collections = new Map();
|
|
2971
|
+
if (dataPath)
|
|
2972
|
+
this._store = new FileStore({ dir: dataPath.replace(/^\w+:\/\//, '') });
|
|
2945
2973
|
}
|
|
2946
2974
|
/** Get collection */
|
|
2947
2975
|
async collection(name) {
|
|
2948
|
-
|
|
2976
|
+
if (!this._collections.has(name)) {
|
|
2977
|
+
const data = await this._store?.load(name, true) || {};
|
|
2978
|
+
this._collections.set(name, new MicroCollection({ data, name }));
|
|
2979
|
+
}
|
|
2980
|
+
return this._collections.get(name);
|
|
2949
2981
|
}
|
|
2950
2982
|
}
|
|
2951
2983
|
/** minimalistic indexed mongo type collection with persistance for usage with Model */
|
|
2952
2984
|
export class MicroCollection {
|
|
2953
|
-
/** Get collections factory */
|
|
2954
|
-
static collections(options) {
|
|
2955
|
-
return new MicroCollections(options);
|
|
2956
|
-
}
|
|
2957
2985
|
constructor(options = {}) {
|
|
2958
2986
|
this.name = options.name || this.constructor.name;
|
|
2959
|
-
const load = options.load ?? (options.store && ((col) => options.store?.load(col.name, true)));
|
|
2960
2987
|
this.data = options.data || {};
|
|
2961
2988
|
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
2989
|
}
|
|
2973
2990
|
/** Query document with query filter */
|
|
2974
2991
|
queryDocument(query, data) {
|
|
@@ -2988,12 +3005,10 @@ export class MicroCollection {
|
|
|
2988
3005
|
}
|
|
2989
3006
|
/** Count all documents */
|
|
2990
3007
|
async countDocuments() {
|
|
2991
|
-
await this.checkReady();
|
|
2992
3008
|
return Object.keys(this.data).length;
|
|
2993
3009
|
}
|
|
2994
3010
|
/** Find one matching document */
|
|
2995
3011
|
async findOne(query) {
|
|
2996
|
-
await this.checkReady();
|
|
2997
3012
|
const id = query._id;
|
|
2998
3013
|
if (id)
|
|
2999
3014
|
return this.queryDocument(query, this.data[id]);
|
|
@@ -3005,7 +3020,6 @@ export class MicroCollection {
|
|
|
3005
3020
|
find(query) {
|
|
3006
3021
|
return {
|
|
3007
3022
|
forEach: async (cb, self) => {
|
|
3008
|
-
await this._ready;
|
|
3009
3023
|
let count = 0;
|
|
3010
3024
|
for (const id in this.data)
|
|
3011
3025
|
if (this.queryDocument(query, this.data[id])) {
|
|
@@ -3018,70 +3032,34 @@ export class MicroCollection {
|
|
|
3018
3032
|
return count;
|
|
3019
3033
|
},
|
|
3020
3034
|
all: async () => {
|
|
3021
|
-
await this._ready;
|
|
3022
3035
|
return Object.values(this.data).filter(doc => this.queryDocument(query, doc));
|
|
3023
3036
|
}
|
|
3024
3037
|
};
|
|
3025
3038
|
}
|
|
3026
3039
|
/** Find and modify one matching document */
|
|
3027
3040
|
async findAndModify(options) {
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
Object.assign(doc, options.update);
|
|
3037
|
-
if (this._save) {
|
|
3038
|
-
if (!this._ready)
|
|
3039
|
-
this._ready = Promise.resolve();
|
|
3040
|
-
this._ready = this._ready.then(async () => {
|
|
3041
|
-
this.data[doc._id] = await this._save?.(doc._id, doc, this) || this.data[doc._id];
|
|
3042
|
-
});
|
|
3043
|
-
}
|
|
3044
|
-
count++;
|
|
3045
|
-
if (options.limit && count >= options.limit)
|
|
3046
|
-
return false;
|
|
3047
|
-
}
|
|
3048
|
-
});
|
|
3049
|
-
return count;
|
|
3050
|
-
}
|
|
3051
|
-
let doc = this.queryDocument(options.query, this.data[id]);
|
|
3052
|
-
if (!doc) {
|
|
3053
|
-
if (!options.upsert && !options.new)
|
|
3054
|
-
throw new InvalidData(`Document not found`);
|
|
3055
|
-
doc = { _id: id };
|
|
3056
|
-
this.data[id] = doc;
|
|
3057
|
-
}
|
|
3058
|
-
else {
|
|
3059
|
-
if (options.new)
|
|
3060
|
-
throw new InvalidData(`Document dupplicate`);
|
|
3061
|
-
}
|
|
3062
|
-
if (options.update) {
|
|
3063
|
-
for (const n in options.update) {
|
|
3041
|
+
const res = await this.findOne(options.query || {});
|
|
3042
|
+
if (res?._id) {
|
|
3043
|
+
await this.updateOne({ _id: res._id }, options.update || {}, options);
|
|
3044
|
+
return this.data[res._id];
|
|
3045
|
+
}
|
|
3046
|
+
if (options.upsert) {
|
|
3047
|
+
const doc = { ...options.query };
|
|
3048
|
+
for (const n in options.update)
|
|
3064
3049
|
if (!n.startsWith('$'))
|
|
3065
3050
|
doc[n] = options.update[n];
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
delete doc[n];
|
|
3070
|
-
}
|
|
3051
|
+
if (options.update?.$set)
|
|
3052
|
+
Object.assign(doc, options.update.$set);
|
|
3053
|
+
return this.insertOne(doc);
|
|
3071
3054
|
}
|
|
3072
|
-
if (this._save)
|
|
3073
|
-
this.data[id] = await this._save(id, doc, this) || doc;
|
|
3074
|
-
return 1;
|
|
3075
3055
|
}
|
|
3076
3056
|
/** Insert one document */
|
|
3077
3057
|
async insertOne(doc) {
|
|
3078
|
-
await this.checkReady();
|
|
3079
3058
|
if (doc._id && this.data[doc._id])
|
|
3080
3059
|
throw new InvalidData(`Document ${doc._id} dupplicate`);
|
|
3060
|
+
doc = { ...doc };
|
|
3081
3061
|
if (!doc._id)
|
|
3082
|
-
doc._id =
|
|
3083
|
-
else
|
|
3084
|
-
doc = { ...doc };
|
|
3062
|
+
doc._id = newObjectId();
|
|
3085
3063
|
this.data[doc._id] = doc;
|
|
3086
3064
|
if (this._save)
|
|
3087
3065
|
this.data[doc._id] = doc = await this._save(doc._id, doc, this) || doc;
|
|
@@ -3089,7 +3067,6 @@ export class MicroCollection {
|
|
|
3089
3067
|
}
|
|
3090
3068
|
/** Insert multiple documents */
|
|
3091
3069
|
async insertMany(docs) {
|
|
3092
|
-
await this.checkReady();
|
|
3093
3070
|
docs.forEach(doc => {
|
|
3094
3071
|
if (doc._id && this.data[doc._id])
|
|
3095
3072
|
throw new InvalidData(`Document ${doc._id} dupplicate`);
|
|
@@ -3100,50 +3077,72 @@ export class MicroCollection {
|
|
|
3100
3077
|
}
|
|
3101
3078
|
/** Delete one matching document */
|
|
3102
3079
|
async deleteOne(query) {
|
|
3103
|
-
|
|
3104
|
-
if (!id)
|
|
3105
|
-
return;
|
|
3106
|
-
await this.checkReady();
|
|
3107
|
-
delete this.data[id];
|
|
3080
|
+
return (await this.updateMany(query, {}, { delete: true, limit: 1 })).modifiedCount;
|
|
3108
3081
|
}
|
|
3109
3082
|
/** Delete all matching documents */
|
|
3110
3083
|
async deleteMany(query) {
|
|
3111
|
-
|
|
3112
|
-
|
|
3084
|
+
return (await this.updateMany(query, {}, { delete: true })).modifiedCount;
|
|
3085
|
+
}
|
|
3086
|
+
async updateOne(query, update, options) {
|
|
3087
|
+
const res = await this.updateMany(query, update, { ...options, limit: 1 });
|
|
3088
|
+
return res;
|
|
3089
|
+
}
|
|
3090
|
+
async updateMany(query, update, options) {
|
|
3091
|
+
let res = { upsertedId: undefined, modifiedCount: 0 };
|
|
3092
|
+
if (!query)
|
|
3093
|
+
return res;
|
|
3094
|
+
let promise = Promise.resolve();
|
|
3113
3095
|
this.find(query).forEach((doc) => {
|
|
3114
3096
|
if (this.queryDocument(query, doc)) {
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3097
|
+
if (options?.delete) {
|
|
3098
|
+
if (!doc._id)
|
|
3099
|
+
return;
|
|
3100
|
+
res.modifiedCount++;
|
|
3101
|
+
if (this._save)
|
|
3102
|
+
promise = promise.then(async () => { doc._id && this._save?.(doc._id, undefined, this); });
|
|
3103
|
+
delete this.data[doc._id];
|
|
3104
|
+
}
|
|
3105
|
+
else {
|
|
3106
|
+
Object.assign(doc, update);
|
|
3107
|
+
res.modifiedCount++;
|
|
3108
|
+
if (this._save) {
|
|
3109
|
+
promise = promise.then(async () => {
|
|
3110
|
+
if (!doc._id)
|
|
3111
|
+
throw new Error('Internal error: missing _id in document');
|
|
3112
|
+
this.data[doc._id] = await this._save?.(doc._id, doc, this) || this.data[doc._id];
|
|
3113
|
+
});
|
|
3114
|
+
}
|
|
3121
3115
|
}
|
|
3116
|
+
if (options?.limit && res.modifiedCount >= options?.limit)
|
|
3117
|
+
return false;
|
|
3122
3118
|
}
|
|
3123
3119
|
});
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
query
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
}
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
}
|
|
3120
|
+
await promise;
|
|
3121
|
+
if (res.modifiedCount || !options?.upsert || options?.delete)
|
|
3122
|
+
return res;
|
|
3123
|
+
if (!query._id)
|
|
3124
|
+
query._id = res.upsertedId = newObjectId();
|
|
3125
|
+
let doc = this.queryDocument(options.query, this.data[query._id]);
|
|
3126
|
+
if (doc)
|
|
3127
|
+
throw new InvalidData(`Document dupplicate`);
|
|
3128
|
+
doc = { _id: query._id, ...query };
|
|
3129
|
+
this.data[query._id] = doc;
|
|
3130
|
+
if (update) {
|
|
3131
|
+
for (const n in update) {
|
|
3132
|
+
if (!n.startsWith('$'))
|
|
3133
|
+
doc[n] = update[n];
|
|
3134
|
+
}
|
|
3135
|
+
if (update.$set) {
|
|
3136
|
+
for (const n in update.$set)
|
|
3137
|
+
doc[n] = update.$set[n];
|
|
3138
|
+
}
|
|
3139
|
+
if (update.$unset) {
|
|
3140
|
+
for (const n in update.$unset)
|
|
3141
|
+
delete doc[n];
|
|
3142
|
+
}
|
|
3143
|
+
}
|
|
3144
|
+
if (this._save)
|
|
3145
|
+
this.data[query._id] = await this._save(query._id, doc, this) || doc;
|
|
3146
|
+
return res;
|
|
3148
3147
|
}
|
|
3149
3148
|
}
|