@radatek/microserver 2.2.1 → 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 +99 -93
- package/microserver.js +99 -63
- 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.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?:
|
|
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,64 +676,78 @@ 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<void>;
|
|
740
743
|
/** Update one matching document */
|
|
741
|
-
update(query:
|
|
744
|
+
update(query: Record<string, any>, options?: ModelContextOptions): Promise<void>;
|
|
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
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:
|
|
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<
|
|
768
|
+
all(): Promise<ModelDocument<T>[]>;
|
|
771
769
|
}
|
|
772
770
|
/** Find options */
|
|
773
771
|
export declare interface FindOptions {
|
|
@@ -782,36 +780,44 @@ 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
|
-
|
|
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?:
|
|
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<
|
|
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:
|
|
807
|
+
insertOne(doc: ModelDocument<TSchema>): Promise<ModelDocument<TSchema>>;
|
|
810
808
|
/** Insert multiple documents */
|
|
811
|
-
|
|
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>;
|
|
814
|
+
updateOne(query: Query, doc: ModelDocument<TSchema>, options?: FindOptions): Promise<{
|
|
815
|
+
upsertedId: any;
|
|
816
|
+
modifiedCount: number;
|
|
817
|
+
}>;
|
|
818
|
+
updateMany(query: Query, update: any, options?: FindOptions): Promise<{
|
|
819
|
+
upsertedId: any;
|
|
820
|
+
modifiedCount: number;
|
|
821
|
+
}>;
|
|
816
822
|
}
|
|
817
823
|
|
package/microserver.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MicroServer
|
|
3
|
-
* @version 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
|
|
816
|
+
const modelName = thisStatic['model:' + key] ?? thisStatic['model'];
|
|
814
817
|
let method = '';
|
|
815
818
|
if (!url)
|
|
816
819
|
key = key.replaceAll('$', '/');
|
|
@@ -833,6 +836,10 @@ export class Controller {
|
|
|
833
836
|
method = keyMatch[1];
|
|
834
837
|
url = keyMatch[2].startsWith('/') ? keyMatch[2] : ('/' + prefix + keyMatch[2]);
|
|
835
838
|
}
|
|
839
|
+
if (!url && !method) {
|
|
840
|
+
method = 'GET';
|
|
841
|
+
url = '/' + prefix + key;
|
|
842
|
+
}
|
|
836
843
|
if (!method)
|
|
837
844
|
return;
|
|
838
845
|
let autoAcl = method.toLowerCase();
|
|
@@ -895,10 +902,18 @@ export class Controller {
|
|
|
895
902
|
list.push((req, res) => {
|
|
896
903
|
res.isJson = true;
|
|
897
904
|
const obj = new this(req, res);
|
|
898
|
-
if (
|
|
899
|
-
req.model =
|
|
905
|
+
if (modelName) {
|
|
906
|
+
req.model = modelName instanceof Model ? modelName : Model.models[modelName];
|
|
900
907
|
if (!obj.model)
|
|
901
|
-
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
|
+
});
|
|
902
917
|
}
|
|
903
918
|
return func.apply(obj, req.paramsList);
|
|
904
919
|
});
|
|
@@ -970,6 +985,8 @@ export class Router extends EventEmitter {
|
|
|
970
985
|
name = 'param:' + name;
|
|
971
986
|
idx = 5;
|
|
972
987
|
}
|
|
988
|
+
if (name === 'json')
|
|
989
|
+
return (req, res) => res.isJson = true;
|
|
973
990
|
if (idx >= 0) {
|
|
974
991
|
const v = name.slice(idx + 1);
|
|
975
992
|
const type = name.slice(0, idx);
|
|
@@ -1686,7 +1703,7 @@ export class MicroServer extends EventEmitter {
|
|
|
1686
1703
|
}
|
|
1687
1704
|
/** Add router hook, alias to `server.router.hook(url, ...args)` */
|
|
1688
1705
|
hook(url, ...args) {
|
|
1689
|
-
this.router.hook(url, args.filter((o) => o));
|
|
1706
|
+
this.router.hook(url, ...args.filter((o) => o));
|
|
1690
1707
|
return this;
|
|
1691
1708
|
}
|
|
1692
1709
|
}
|
|
@@ -2557,26 +2574,31 @@ function newObjectId() {
|
|
|
2557
2574
|
return (new Date().getTime() / 1000 | 0).toString(16) + globalObjectId.toString('hex');
|
|
2558
2575
|
}
|
|
2559
2576
|
export class Model {
|
|
2577
|
+
static schema(schema) {
|
|
2578
|
+
return schema;
|
|
2579
|
+
}
|
|
2560
2580
|
/** Define model */
|
|
2561
|
-
static define(name,
|
|
2581
|
+
static define(name, schema, options) {
|
|
2562
2582
|
options = options || {};
|
|
2563
2583
|
if (!options.collection && this.collections)
|
|
2564
2584
|
options.collection = this.collections.collection(name);
|
|
2565
2585
|
const inst = options?.class
|
|
2566
|
-
? new options.class(
|
|
2567
|
-
: new Model(
|
|
2586
|
+
? new options.class(schema, { name, ...options })
|
|
2587
|
+
: new Model(schema, { name, ...options });
|
|
2568
2588
|
Model.models[name] = inst;
|
|
2569
2589
|
return inst;
|
|
2570
2590
|
}
|
|
2571
2591
|
/** Create model acording to description */
|
|
2572
|
-
constructor(
|
|
2592
|
+
constructor(schema, options) {
|
|
2593
|
+
/** Custom options */
|
|
2594
|
+
this.options = {};
|
|
2573
2595
|
const model = this.model = {};
|
|
2574
|
-
this.name = options?.name || this.__proto__.constructor.name;
|
|
2596
|
+
this.options.name = options?.name || this.__proto__.constructor.name;
|
|
2575
2597
|
this.collection = options?.collection;
|
|
2576
2598
|
this.handler = this.handler.bind(this);
|
|
2577
|
-
for (const n in
|
|
2599
|
+
for (const n in schema) {
|
|
2578
2600
|
const modelField = this.model[n] = { name: n };
|
|
2579
|
-
let field =
|
|
2601
|
+
let field = schema[n];
|
|
2580
2602
|
let fieldType, isArray = false;
|
|
2581
2603
|
if (typeof field === 'object' && !Array.isArray(field) && !(field instanceof Model))
|
|
2582
2604
|
fieldType = field.type;
|
|
@@ -2594,7 +2616,7 @@ export class Model {
|
|
|
2594
2616
|
if (fieldType instanceof Model) {
|
|
2595
2617
|
modelField.model = fieldType;
|
|
2596
2618
|
fieldType = 'model';
|
|
2597
|
-
validateType = (value, options) => modelField.model?.
|
|
2619
|
+
validateType = (value, options) => modelField.model?.document(value, options);
|
|
2598
2620
|
}
|
|
2599
2621
|
else {
|
|
2600
2622
|
fieldType = fieldType.toString().toLowerCase();
|
|
@@ -2754,8 +2776,12 @@ export class Model {
|
|
|
2754
2776
|
validators.push(field.validate);
|
|
2755
2777
|
}
|
|
2756
2778
|
}
|
|
2779
|
+
/** Get model name */
|
|
2780
|
+
get name() {
|
|
2781
|
+
return this.options.name;
|
|
2782
|
+
}
|
|
2757
2783
|
/** Validate data over model */
|
|
2758
|
-
|
|
2784
|
+
document(data, options) {
|
|
2759
2785
|
options = options || {};
|
|
2760
2786
|
const prefix = options.name ? options.name + '.' : '';
|
|
2761
2787
|
if (options.validate === false)
|
|
@@ -2763,7 +2789,7 @@ export class Model {
|
|
|
2763
2789
|
const res = {};
|
|
2764
2790
|
for (const name in this.model) {
|
|
2765
2791
|
const field = this.model[name];
|
|
2766
|
-
const paramOptions = { ...options, field, name: prefix + name, model: this };
|
|
2792
|
+
const paramOptions = { ...this.options, ...options, field, name: prefix + name, model: this };
|
|
2767
2793
|
const canWrite = field.canWrite(paramOptions), canRead = field.canRead(paramOptions), required = field.required?.(paramOptions);
|
|
2768
2794
|
if (options.readOnly) {
|
|
2769
2795
|
if (canRead === false || !field.type)
|
|
@@ -2831,7 +2857,7 @@ export class Model {
|
|
|
2831
2857
|
for (const name in this.model) {
|
|
2832
2858
|
if (!(name in res)) {
|
|
2833
2859
|
const field = this.model[name];
|
|
2834
|
-
const paramOptions = { ...options, field, name, model: this };
|
|
2860
|
+
const paramOptions = { ...this.options, ...options, field, name, model: this };
|
|
2835
2861
|
if ((!options?.required && name in data) || (field.required && field.default)) {
|
|
2836
2862
|
if (typeof field.required === 'function' && field.required(paramOptions) && field.default && (!(name in data) || field.canWrite(options) === false))
|
|
2837
2863
|
res[name] = options?.default !== false ? field.default.length ? field.default(paramOptions) : field.default() : data[name];
|
|
@@ -2853,8 +2879,9 @@ export class Model {
|
|
|
2853
2879
|
this.collection = await this.collection;
|
|
2854
2880
|
if (!this.collection)
|
|
2855
2881
|
throw new AccessDenied('Database not configured');
|
|
2856
|
-
|
|
2857
|
-
|
|
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;
|
|
2858
2885
|
}
|
|
2859
2886
|
/** Find many documents */
|
|
2860
2887
|
async findMany(query, options) {
|
|
@@ -2863,7 +2890,8 @@ export class Model {
|
|
|
2863
2890
|
if (!this.collection)
|
|
2864
2891
|
throw new AccessDenied('Database not configured');
|
|
2865
2892
|
const res = [];
|
|
2866
|
-
|
|
2893
|
+
options = { readOnly: true, ...this.options, ...options };
|
|
2894
|
+
await this.collection.find(this.getFilter(query || {}, options)).forEach((doc) => res.push(this.document(doc, options)));
|
|
2867
2895
|
return res;
|
|
2868
2896
|
}
|
|
2869
2897
|
/** Insert a new document */
|
|
@@ -2876,8 +2904,9 @@ export class Model {
|
|
|
2876
2904
|
this.collection = await this.collection;
|
|
2877
2905
|
if (!this.collection)
|
|
2878
2906
|
throw new AccessDenied('Database not configured');
|
|
2907
|
+
options = { ...this.options, ...options };
|
|
2879
2908
|
if (options?.validate !== false)
|
|
2880
|
-
query = this.
|
|
2909
|
+
query = this.document(query, options);
|
|
2881
2910
|
const unset = query.$unset || {};
|
|
2882
2911
|
for (const n in query) {
|
|
2883
2912
|
if (query[n] === undefined || query[n] === null) {
|
|
@@ -2895,7 +2924,7 @@ export class Model {
|
|
|
2895
2924
|
if (!this.collection)
|
|
2896
2925
|
throw new AccessDenied('Database not configured');
|
|
2897
2926
|
if (query._id)
|
|
2898
|
-
await this.collection.deleteOne(this.getFilter(query, options));
|
|
2927
|
+
await this.collection.deleteOne(this.getFilter(query, { ...this.options, ...options }));
|
|
2899
2928
|
}
|
|
2900
2929
|
/** Microserver middleware */
|
|
2901
2930
|
handler(req, res) {
|
|
@@ -2933,36 +2962,27 @@ export class Model {
|
|
|
2933
2962
|
}
|
|
2934
2963
|
Model.models = {};
|
|
2935
2964
|
/** Collection factory */
|
|
2936
|
-
class
|
|
2937
|
-
constructor(
|
|
2938
|
-
this.
|
|
2965
|
+
export class MicroCollectionStore {
|
|
2966
|
+
constructor(dataPath) {
|
|
2967
|
+
this._collections = new Map();
|
|
2968
|
+
if (dataPath)
|
|
2969
|
+
this._store = new FileStore({ dir: dataPath.replace(/^\w+:\/\//, '') });
|
|
2939
2970
|
}
|
|
2940
2971
|
/** Get collection */
|
|
2941
2972
|
async collection(name) {
|
|
2942
|
-
|
|
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);
|
|
2943
2978
|
}
|
|
2944
2979
|
}
|
|
2945
2980
|
/** minimalistic indexed mongo type collection with persistance for usage with Model */
|
|
2946
2981
|
export class MicroCollection {
|
|
2947
|
-
/** Get collections factory */
|
|
2948
|
-
static collections(options) {
|
|
2949
|
-
return new MicroCollections(options);
|
|
2950
|
-
}
|
|
2951
2982
|
constructor(options = {}) {
|
|
2952
2983
|
this.name = options.name || this.constructor.name;
|
|
2953
|
-
const load = options.load ?? (options.store && ((col) => options.store?.load(col.name, true)));
|
|
2954
2984
|
this.data = options.data || {};
|
|
2955
2985
|
this._save = options.save;
|
|
2956
|
-
this._ready = load?.(this)?.catch(() => { }).then(data => {
|
|
2957
|
-
this.data = data || {};
|
|
2958
|
-
});
|
|
2959
|
-
}
|
|
2960
|
-
/** check for collection is ready */
|
|
2961
|
-
async checkReady() {
|
|
2962
|
-
if (this._ready) {
|
|
2963
|
-
await this._ready;
|
|
2964
|
-
this._ready = undefined;
|
|
2965
|
-
}
|
|
2966
2986
|
}
|
|
2967
2987
|
/** Query document with query filter */
|
|
2968
2988
|
queryDocument(query, data) {
|
|
@@ -2981,13 +3001,11 @@ export class MicroCollection {
|
|
|
2981
3001
|
return data;
|
|
2982
3002
|
}
|
|
2983
3003
|
/** Count all documents */
|
|
2984
|
-
async
|
|
2985
|
-
await this.checkReady();
|
|
3004
|
+
async countDocuments() {
|
|
2986
3005
|
return Object.keys(this.data).length;
|
|
2987
3006
|
}
|
|
2988
3007
|
/** Find one matching document */
|
|
2989
3008
|
async findOne(query) {
|
|
2990
|
-
await this.checkReady();
|
|
2991
3009
|
const id = query._id;
|
|
2992
3010
|
if (id)
|
|
2993
3011
|
return this.queryDocument(query, this.data[id]);
|
|
@@ -2999,7 +3017,6 @@ export class MicroCollection {
|
|
|
2999
3017
|
find(query) {
|
|
3000
3018
|
return {
|
|
3001
3019
|
forEach: async (cb, self) => {
|
|
3002
|
-
await this._ready;
|
|
3003
3020
|
let count = 0;
|
|
3004
3021
|
for (const id in this.data)
|
|
3005
3022
|
if (this.queryDocument(query, this.data[id])) {
|
|
@@ -3012,7 +3029,6 @@ export class MicroCollection {
|
|
|
3012
3029
|
return count;
|
|
3013
3030
|
},
|
|
3014
3031
|
all: async () => {
|
|
3015
|
-
await this._ready;
|
|
3016
3032
|
return Object.values(this.data).filter(doc => this.queryDocument(query, doc));
|
|
3017
3033
|
}
|
|
3018
3034
|
};
|
|
@@ -3021,17 +3037,17 @@ export class MicroCollection {
|
|
|
3021
3037
|
async findAndModify(options) {
|
|
3022
3038
|
if (!options.query)
|
|
3023
3039
|
return 0;
|
|
3024
|
-
await this.checkReady();
|
|
3025
3040
|
const id = ((options.upsert || options.new) && !options.query._id) ? newObjectId() : options.query._id;
|
|
3026
3041
|
if (!id) {
|
|
3027
3042
|
let count = 0;
|
|
3043
|
+
let promise = Promise.resolve();
|
|
3028
3044
|
this.find(options.query).forEach((doc) => {
|
|
3029
3045
|
if (this.queryDocument(options.query, doc)) {
|
|
3030
3046
|
Object.assign(doc, options.update);
|
|
3031
3047
|
if (this._save) {
|
|
3032
|
-
|
|
3033
|
-
|
|
3034
|
-
|
|
3048
|
+
promise = promise.then(async () => {
|
|
3049
|
+
if (!doc._id)
|
|
3050
|
+
throw new Error('Internal error: missing _id in document');
|
|
3035
3051
|
this.data[doc._id] = await this._save?.(doc._id, doc, this) || this.data[doc._id];
|
|
3036
3052
|
});
|
|
3037
3053
|
}
|
|
@@ -3040,6 +3056,7 @@ export class MicroCollection {
|
|
|
3040
3056
|
return false;
|
|
3041
3057
|
}
|
|
3042
3058
|
});
|
|
3059
|
+
await promise;
|
|
3043
3060
|
return count;
|
|
3044
3061
|
}
|
|
3045
3062
|
let doc = this.queryDocument(options.query, this.data[id]);
|
|
@@ -3069,21 +3086,18 @@ export class MicroCollection {
|
|
|
3069
3086
|
}
|
|
3070
3087
|
/** Insert one document */
|
|
3071
3088
|
async insertOne(doc) {
|
|
3072
|
-
await this.checkReady();
|
|
3073
3089
|
if (doc._id && this.data[doc._id])
|
|
3074
3090
|
throw new InvalidData(`Document ${doc._id} dupplicate`);
|
|
3091
|
+
doc = { ...doc };
|
|
3075
3092
|
if (!doc._id)
|
|
3076
|
-
doc._id =
|
|
3077
|
-
else
|
|
3078
|
-
doc = { ...doc };
|
|
3093
|
+
doc._id = newObjectId();
|
|
3079
3094
|
this.data[doc._id] = doc;
|
|
3080
3095
|
if (this._save)
|
|
3081
3096
|
this.data[doc._id] = doc = await this._save(doc._id, doc, this) || doc;
|
|
3082
3097
|
return doc;
|
|
3083
3098
|
}
|
|
3084
3099
|
/** Insert multiple documents */
|
|
3085
|
-
async
|
|
3086
|
-
await this.checkReady();
|
|
3100
|
+
async insertMany(docs) {
|
|
3087
3101
|
docs.forEach(doc => {
|
|
3088
3102
|
if (doc._id && this.data[doc._id])
|
|
3089
3103
|
throw new InvalidData(`Document ${doc._id} dupplicate`);
|
|
@@ -3097,24 +3111,46 @@ export class MicroCollection {
|
|
|
3097
3111
|
const id = query._id;
|
|
3098
3112
|
if (!id)
|
|
3099
3113
|
return;
|
|
3100
|
-
await this.checkReady();
|
|
3101
3114
|
delete this.data[id];
|
|
3102
3115
|
}
|
|
3103
3116
|
/** Delete all matching documents */
|
|
3104
3117
|
async deleteMany(query) {
|
|
3105
3118
|
let count = 0;
|
|
3106
|
-
|
|
3119
|
+
let promise = Promise.resolve();
|
|
3107
3120
|
this.find(query).forEach((doc) => {
|
|
3108
3121
|
if (this.queryDocument(query, doc)) {
|
|
3109
3122
|
count++;
|
|
3123
|
+
if (!doc._id)
|
|
3124
|
+
return;
|
|
3110
3125
|
delete this.data[doc._id];
|
|
3111
|
-
if (this._save)
|
|
3112
|
-
|
|
3113
|
-
this._ready = Promise.resolve();
|
|
3114
|
-
this._ready = this._ready.then(async () => { this._save?.(doc._id, undefined, this); });
|
|
3115
|
-
}
|
|
3126
|
+
if (this._save)
|
|
3127
|
+
promise = promise.then(async () => { doc._id && this._save?.(doc._id, undefined, this); });
|
|
3116
3128
|
}
|
|
3117
3129
|
});
|
|
3130
|
+
await promise;
|
|
3118
3131
|
return count;
|
|
3119
3132
|
}
|
|
3133
|
+
async updateOne(query, doc, options) {
|
|
3134
|
+
const count = await this.findAndModify({
|
|
3135
|
+
query,
|
|
3136
|
+
update: doc,
|
|
3137
|
+
upsert: options?.upsert,
|
|
3138
|
+
limit: 1
|
|
3139
|
+
});
|
|
3140
|
+
return {
|
|
3141
|
+
upsertedId: undefined,
|
|
3142
|
+
modifiedCount: count
|
|
3143
|
+
};
|
|
3144
|
+
}
|
|
3145
|
+
async updateMany(query, update, options) {
|
|
3146
|
+
const count = await this.findAndModify({
|
|
3147
|
+
...options,
|
|
3148
|
+
query,
|
|
3149
|
+
update
|
|
3150
|
+
});
|
|
3151
|
+
return {
|
|
3152
|
+
upsertedId: undefined,
|
|
3153
|
+
modifiedCount: count
|
|
3154
|
+
};
|
|
3155
|
+
}
|
|
3120
3156
|
}
|