dolphin-server-modules 1.0.1 → 1.0.3

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/README.md CHANGED
@@ -90,7 +90,27 @@ export const { GET, POST, PUT, DELETE } = createNextAppRoute(postController);
90
90
 
91
91
  ---
92
92
 
93
- ## 4. Zod Validation Middleware (`/middleware/zod`)
93
+ ## 🍃 4. Mongoose Adapter (`/adapters/mongoose`)
94
+ An official, fully typed Mongoose adapter for seamless integration with the Auth and CRUD modules. It handles mapping `id` to `_id`, query mapping (`$like` to `$regex`), and works independently!
95
+
96
+ ```typescript
97
+ import { createMongooseAdapter } from 'dolphin-server-modules/adapters/mongoose';
98
+ import { User, RefreshToken, Post } from './my-mongoose-models';
99
+
100
+ const dbAdapter = createMongooseAdapter({
101
+ User,
102
+ RefreshToken,
103
+ models: {
104
+ posts: Post // Maps the 'posts' collection to the generic Post model
105
+ }
106
+ });
107
+
108
+ // Now pass dbAdapter to createAuth or createCRUD initializers!
109
+ ```
110
+
111
+ ---
112
+
113
+ ## ✅ 5. Zod Validation Middleware (`/middleware/zod`)
94
114
  Validate your data effortlessly using `zod`. Protect your endpoints from bad data.
95
115
 
96
116
  ```typescript
@@ -0,0 +1,142 @@
1
+ import type { Model } from 'mongoose';
2
+
3
+ export interface MongooseAdapterConfig {
4
+ User: Model<any>;
5
+ RefreshToken: Model<any>;
6
+ models?: Record<string, Model<any>>;
7
+ }
8
+
9
+ export function createMongooseAdapter(config: MongooseAdapterConfig) {
10
+ // Map MongoDB document to the standard BaseDocument shape
11
+ const mapDoc = (doc: any) => {
12
+ if (!doc) return null;
13
+ const obj = doc.toObject && typeof doc.toObject === 'function' ? doc.toObject() : { ...doc };
14
+ if (obj._id) {
15
+ obj.id = obj._id.toString();
16
+ delete obj._id;
17
+ }
18
+ delete obj.__v;
19
+ return obj;
20
+ };
21
+
22
+ // Convert standard QueryFilter to Mongoose Query format
23
+ const mapQuery = (query: any) => {
24
+ if (!query) return {};
25
+ const parsed: any = {};
26
+ for (const [key, val] of Object.entries(query)) {
27
+ if (key === 'id') {
28
+ parsed['_id'] = val;
29
+ } else if (key === '$and' || key === '$or') {
30
+ parsed[key] = (val as any[]).map(mapQuery);
31
+ } else if (typeof val === 'object' && val !== null && !Array.isArray(val)) {
32
+ const ops: any = {};
33
+ for (const [op, opVal] of Object.entries(val)) {
34
+ if (op === '$like') {
35
+ ops['$regex'] = opVal;
36
+ ops['$options'] = 'i';
37
+ } else {
38
+ ops[op] = opVal;
39
+ }
40
+ }
41
+ parsed[key] = ops;
42
+ } else {
43
+ parsed[key] = val;
44
+ }
45
+ }
46
+ return parsed;
47
+ };
48
+
49
+ // Convert standard sorting to Mongoose sort format
50
+ const mapSort = (sort?: Record<string, 'asc' | 'desc'>) => {
51
+ if (!sort) return undefined;
52
+ const result: Record<string, 1 | -1> = {};
53
+ for (const [k, v] of Object.entries(sort)) {
54
+ result[k] = v === 'asc' ? 1 : -1;
55
+ }
56
+ return result;
57
+ };
58
+
59
+ // Helper to fetch generic CRUD models
60
+ const getModel = (collection: string): Model<any> => {
61
+ if (!config.models || !config.models[collection]) {
62
+ throw new Error(`Model for collection '${collection}' not found in MongooseAdapterConfig`);
63
+ }
64
+ return config.models[collection];
65
+ };
66
+
67
+ return {
68
+ // ==========================================
69
+ // AUTHENTICATION ADAPTER METHODS
70
+ // ==========================================
71
+ async createUser(data: any) {
72
+ const user = await config.User.create(data);
73
+ return mapDoc(user);
74
+ },
75
+ async findUserByEmail(email: string) {
76
+ const user = await config.User.findOne({ email });
77
+ return mapDoc(user);
78
+ },
79
+ async findUserById(id: string) {
80
+ const user = await config.User.findById(id);
81
+ return mapDoc(user);
82
+ },
83
+ async updateUser(id: string, data: any) {
84
+ const user = await config.User.findByIdAndUpdate(id, data, { new: true });
85
+ return mapDoc(user);
86
+ },
87
+ async saveRefreshToken(data: any) {
88
+ await config.RefreshToken.create(data);
89
+ },
90
+ async findRefreshToken(token: string) {
91
+ const rt = await config.RefreshToken.findOne({ token });
92
+ return mapDoc(rt);
93
+ },
94
+ async deleteRefreshToken(token: string) {
95
+ await config.RefreshToken.deleteOne({ token });
96
+ },
97
+
98
+ // ==========================================
99
+ // CRUD ADAPTER METHODS
100
+ // ==========================================
101
+ async create(collection: string, data: any) {
102
+ const Model = getModel(collection);
103
+ const doc = await Model.create(data);
104
+ return mapDoc(doc);
105
+ },
106
+ async read(collection: string, query: any) {
107
+ const Model = getModel(collection);
108
+ const docs = await Model.find(mapQuery(query));
109
+ return docs.map(mapDoc);
110
+ },
111
+ async update(collection: string, query: any, data: any) {
112
+ const Model = getModel(collection);
113
+ await Model.updateMany(mapQuery(query), data);
114
+
115
+ // Return updated documents if we can map them back, otherwise minimal info
116
+ // Since updateMany doesn't return the updated docs in mongoose, we can just return a generic success object
117
+ // or fetch them. The interface expects Promise<any>. We will fetch them to match typical behavior.
118
+ const docs = await Model.find(mapQuery({ ...query, ...data }));
119
+ return docs.map(mapDoc);
120
+ },
121
+ async delete(collection: string, query: any) {
122
+ const Model = getModel(collection);
123
+ await Model.deleteMany(mapQuery(query));
124
+ return { deleted: true };
125
+ },
126
+ async advancedRead(collection: string, query: any, options: any) {
127
+ const Model = getModel(collection);
128
+ let mQuery = Model.find(mapQuery(query));
129
+ if (options.sort) {
130
+ mQuery = mQuery.sort(mapSort(options.sort));
131
+ }
132
+ if (options.offset !== undefined) {
133
+ mQuery = mQuery.skip(options.offset);
134
+ }
135
+ if (options.limit !== undefined) {
136
+ mQuery = mQuery.limit(options.limit);
137
+ }
138
+ const docs = await mQuery;
139
+ return docs.map(mapDoc);
140
+ }
141
+ };
142
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export * from './auth/auth';
2
2
  export * from './controller/controller';
3
3
  export { createCRUD, BaseDocument, QueryFilter, PaginationOptions, DatabaseAdapter as CrudDatabaseAdapter } from './curd/crud';
4
+ export * from './server/server';
5
+ export * from './router/router';
package/dist/index.js CHANGED
@@ -22,4 +22,7 @@ __exportStar(require("./controller/controller"), exports);
22
22
  // Re-export CRUD but alias DatabaseAdapter to avoid conflicts
23
23
  var crud_1 = require("./curd/crud");
24
24
  Object.defineProperty(exports, "createCRUD", { enumerable: true, get: function () { return crud_1.createCRUD; } });
25
+ // Re-export Server & Router
26
+ __exportStar(require("./server/server"), exports);
27
+ __exportStar(require("./router/router"), exports);
25
28
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,iBAAiB;AACjB,8CAA4B;AAC5B,uBAAuB;AACvB,0DAAwC;AACxC,8DAA8D;AAC9D,oCAMqB;AALnB,kGAAA,UAAU,OAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA,iBAAiB;AACjB,8CAA4B;AAC5B,uBAAuB;AACvB,0DAAwC;AACxC,8DAA8D;AAC9D,oCAMqB;AALnB,kGAAA,UAAU,OAAA;AAMZ,4BAA4B;AAC5B,kDAAgC;AAChC,kDAAgC"}
@@ -0,0 +1,13 @@
1
+ type Handler = (req: any, res: any) => Promise<void> | void;
2
+ export declare function createDolphinRouter(): {
3
+ get: (path: string, handler: Handler) => void;
4
+ post: (path: string, handler: Handler) => void;
5
+ put: (path: string, handler: Handler) => void;
6
+ delete: (path: string, handler: Handler) => void;
7
+ patch: (path: string, handler: Handler) => void;
8
+ match(method: string, url: string): {
9
+ handler: Handler;
10
+ params: Record<string, string>;
11
+ } | null;
12
+ };
13
+ export {};
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createDolphinRouter = createDolphinRouter;
4
+ function createDolphinRouter() {
5
+ const routes = [];
6
+ const addRoute = (method, path, handler) => {
7
+ // Convert /users/:id to regex
8
+ const keys = [];
9
+ const pattern = path
10
+ .replace(/:([^\/]+)/g, (_, key) => {
11
+ keys.push(key);
12
+ return '([^\\/]+)';
13
+ })
14
+ .replace(/\//g, '\\/');
15
+ routes.push({
16
+ method: method.toUpperCase(),
17
+ path,
18
+ handler,
19
+ regex: new RegExp(`^${pattern}$`),
20
+ keys
21
+ });
22
+ };
23
+ return {
24
+ get: (path, handler) => addRoute('GET', path, handler),
25
+ post: (path, handler) => addRoute('POST', path, handler),
26
+ put: (path, handler) => addRoute('PUT', path, handler),
27
+ delete: (path, handler) => addRoute('DELETE', path, handler),
28
+ patch: (path, handler) => addRoute('PATCH', path, handler),
29
+ match(method, url) {
30
+ const path = url.split('?')[0];
31
+ const m = method.toUpperCase();
32
+ for (const route of routes) {
33
+ if (route.method !== m && route.method !== 'ALL')
34
+ continue;
35
+ const match = path.match(route.regex);
36
+ if (match) {
37
+ const params = {};
38
+ route.keys.forEach((key, i) => {
39
+ params[key] = match[i + 1];
40
+ });
41
+ return { handler: route.handler, params };
42
+ }
43
+ }
44
+ return null;
45
+ }
46
+ };
47
+ }
48
+ //# sourceMappingURL=router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"router.js","sourceRoot":"","sources":["../../router/router.ts"],"names":[],"mappings":";;AAUA,kDAgDC;AAhDD,SAAgB,mBAAmB;IACjC,MAAM,MAAM,GAAY,EAAE,CAAC;IAE3B,MAAM,QAAQ,GAAG,CAAC,MAAc,EAAE,IAAY,EAAE,OAAgB,EAAE,EAAE;QAClE,8BAA8B;QAC9B,MAAM,IAAI,GAAa,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,IAAI;aACjB,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,EAAE;YAChC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YACf,OAAO,WAAW,CAAC;QACrB,CAAC,CAAC;aACD,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;QAEzB,MAAM,CAAC,IAAI,CAAC;YACV,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE;YAC5B,IAAI;YACJ,OAAO;YACP,KAAK,EAAE,IAAI,MAAM,CAAC,IAAI,OAAO,GAAG,CAAC;YACjC,IAAI;SACL,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,OAAO;QACL,GAAG,EAAE,CAAC,IAAY,EAAE,OAAgB,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC;QACvE,IAAI,EAAE,CAAC,IAAY,EAAE,OAAgB,EAAE,EAAE,CAAC,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC;QACzE,GAAG,EAAE,CAAC,IAAY,EAAE,OAAgB,EAAE,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC;QACvE,MAAM,EAAE,CAAC,IAAY,EAAE,OAAgB,EAAE,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,OAAO,CAAC;QAC7E,KAAK,EAAE,CAAC,IAAY,EAAE,OAAgB,EAAE,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC;QAE3E,KAAK,CAAC,MAAc,EAAE,GAAW;YAC/B,MAAM,IAAI,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/B,MAAM,CAAC,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;YAE/B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;gBAC3B,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK;oBAAE,SAAS;gBAE3D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;gBACtC,IAAI,KAAK,EAAE,CAAC;oBACV,MAAM,MAAM,GAA2B,EAAE,CAAC;oBAC1C,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE;wBAC5B,MAAM,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;oBAC7B,CAAC,CAAC,CAAC;oBACH,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC;gBAC5C,CAAC;YACH,CAAC;YACD,OAAO,IAAI,CAAC;QACd,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,18 @@
1
+ import http from 'node:http';
2
+ export declare function createDolphinServer(options?: {
3
+ port?: number;
4
+ host?: string;
5
+ }): {
6
+ use: (mw: any) => number;
7
+ listen: (port?: number, callback?: () => void) => void;
8
+ close: () => http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
9
+ get: (path: string, handler: (req: any, res: any) => Promise<void> | void) => void;
10
+ post: (path: string, handler: (req: any, res: any) => Promise<void> | void) => void;
11
+ put: (path: string, handler: (req: any, res: any) => Promise<void> | void) => void;
12
+ delete: (path: string, handler: (req: any, res: any) => Promise<void> | void) => void;
13
+ patch: (path: string, handler: (req: any, res: any) => Promise<void> | void) => void;
14
+ match(method: string, url: string): {
15
+ handler: (req: any, res: any) => Promise<void> | void;
16
+ params: Record<string, string>;
17
+ } | null;
18
+ };
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createDolphinServer = createDolphinServer;
7
+ const node_http_1 = __importDefault(require("node:http"));
8
+ const router_1 = require("../router/router");
9
+ function createDolphinServer(options = {}) {
10
+ const router = (0, router_1.createDolphinRouter)();
11
+ const middlewares = [];
12
+ const server = node_http_1.default.createServer(async (req, res) => {
13
+ // Utility for JSON responses
14
+ res.json = (data, status = 200) => {
15
+ res.writeHead(status, { 'Content-Type': 'application/json' });
16
+ res.end(JSON.stringify(data));
17
+ };
18
+ // Global middleware execution
19
+ for (const mw of middlewares) {
20
+ const next = new Promise(resolve => mw(req, res, resolve));
21
+ await next;
22
+ if (res.writableEnded)
23
+ return;
24
+ }
25
+ // Body parsing for POST/PUT/PATCH
26
+ if (['POST', 'PUT', 'PATCH'].includes(req.method) && req.headers['content-type']?.includes('application/json')) {
27
+ const chunks = [];
28
+ for await (const chunk of req)
29
+ chunks.push(chunk);
30
+ try {
31
+ req.body = JSON.parse(Buffer.concat(chunks).toString());
32
+ }
33
+ catch {
34
+ req.body = {};
35
+ }
36
+ }
37
+ // Matching route
38
+ const match = router.match(req.method, req.url);
39
+ if (match) {
40
+ req.params = match.params;
41
+ try {
42
+ await match.handler(req, res);
43
+ }
44
+ catch (err) {
45
+ res.json({ error: err.message || 'Internal Server Error' }, err.status || 500);
46
+ }
47
+ }
48
+ else {
49
+ res.json({ error: 'Not Found' }, 404);
50
+ }
51
+ });
52
+ return {
53
+ ...router, // Mixin router methods (get, post, etc.)
54
+ use: (mw) => middlewares.push(mw),
55
+ listen: (port = options.port || 3000, callback) => {
56
+ server.listen(port, options.host || '0.0.0.0', callback);
57
+ },
58
+ close: () => server.close()
59
+ };
60
+ }
61
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../server/server.ts"],"names":[],"mappings":"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"}
package/index.ts CHANGED
@@ -10,3 +10,6 @@ export {
10
10
  PaginationOptions,
11
11
  DatabaseAdapter as CrudDatabaseAdapter
12
12
  } from './curd/crud';
13
+ // Re-export Server & Router
14
+ export * from './server/server';
15
+ export * from './router/router';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dolphin-server-modules",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Core utility modules for Auth, CRUD, and Controllers",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,7 +9,10 @@
9
9
  "./auth": "./dist/auth/auth.js",
10
10
  "./controller": "./dist/controller/controller.js",
11
11
  "./crud": "./dist/curd/crud.js",
12
- "./middleware/zod": "./dist/middleware/zod.js"
12
+ "./middleware/zod": "./dist/middleware/zod.js",
13
+ "./adapters/mongoose": "./dist/adapters/mongoose/index.js",
14
+ "./server": "./dist/server/server.js",
15
+ "./router": "./dist/router/router.js"
13
16
  },
14
17
  "scripts": {
15
18
  "build": "tsc",
@@ -33,6 +36,7 @@
33
36
  "@types/jest": "^29.5.12",
34
37
  "@types/node": "^20.11.30",
35
38
  "jest": "^29.7.0",
39
+ "mongoose": "^9.3.2",
36
40
  "ts-jest": "^29.1.2",
37
41
  "typescript": "^5.4.3"
38
42
  }
@@ -0,0 +1,59 @@
1
+ type Handler = (req: any, res: any) => Promise<void> | void;
2
+
3
+ interface Route {
4
+ method: string;
5
+ path: string;
6
+ handler: Handler;
7
+ regex: RegExp;
8
+ keys: string[];
9
+ }
10
+
11
+ export function createDolphinRouter() {
12
+ const routes: Route[] = [];
13
+
14
+ const addRoute = (method: string, path: string, handler: Handler) => {
15
+ // Convert /users/:id to regex
16
+ const keys: string[] = [];
17
+ const pattern = path
18
+ .replace(/:([^\/]+)/g, (_, key) => {
19
+ keys.push(key);
20
+ return '([^\\/]+)';
21
+ })
22
+ .replace(/\//g, '\\/');
23
+
24
+ routes.push({
25
+ method: method.toUpperCase(),
26
+ path,
27
+ handler,
28
+ regex: new RegExp(`^${pattern}$`),
29
+ keys
30
+ });
31
+ };
32
+
33
+ return {
34
+ get: (path: string, handler: Handler) => addRoute('GET', path, handler),
35
+ post: (path: string, handler: Handler) => addRoute('POST', path, handler),
36
+ put: (path: string, handler: Handler) => addRoute('PUT', path, handler),
37
+ delete: (path: string, handler: Handler) => addRoute('DELETE', path, handler),
38
+ patch: (path: string, handler: Handler) => addRoute('PATCH', path, handler),
39
+
40
+ match(method: string, url: string) {
41
+ const path = url.split('?')[0];
42
+ const m = method.toUpperCase();
43
+
44
+ for (const route of routes) {
45
+ if (route.method !== m && route.method !== 'ALL') continue;
46
+
47
+ const match = path.match(route.regex);
48
+ if (match) {
49
+ const params: Record<string, string> = {};
50
+ route.keys.forEach((key, i) => {
51
+ params[key] = match[i + 1];
52
+ });
53
+ return { handler: route.handler, params };
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+ };
59
+ }
@@ -0,0 +1,55 @@
1
+ import http from 'node:http';
2
+ import { createDolphinRouter } from '../router/router';
3
+
4
+ export function createDolphinServer(options: { port?: number; host?: string } = {}) {
5
+ const router = createDolphinRouter();
6
+ const middlewares: any[] = [];
7
+
8
+ const server = http.createServer(async (req: any, res: any) => {
9
+ // Utility for JSON responses
10
+ res.json = (data: any, status = 200) => {
11
+ res.writeHead(status, { 'Content-Type': 'application/json' });
12
+ res.end(JSON.stringify(data));
13
+ };
14
+
15
+ // Global middleware execution
16
+ for (const mw of middlewares) {
17
+ const next = new Promise(resolve => mw(req, res, resolve));
18
+ await next;
19
+ if (res.writableEnded) return;
20
+ }
21
+
22
+ // Body parsing for POST/PUT/PATCH
23
+ if (['POST', 'PUT', 'PATCH'].includes(req.method!) && req.headers['content-type']?.includes('application/json')) {
24
+ const chunks: any[] = [];
25
+ for await (const chunk of req) chunks.push(chunk);
26
+ try {
27
+ req.body = JSON.parse(Buffer.concat(chunks).toString());
28
+ } catch {
29
+ req.body = {};
30
+ }
31
+ }
32
+
33
+ // Matching route
34
+ const match = router.match(req.method!, req.url!);
35
+ if (match) {
36
+ req.params = match.params;
37
+ try {
38
+ await match.handler(req, res);
39
+ } catch (err: any) {
40
+ res.json({ error: err.message || 'Internal Server Error' }, err.status || 500);
41
+ }
42
+ } else {
43
+ res.json({ error: 'Not Found' }, 404);
44
+ }
45
+ });
46
+
47
+ return {
48
+ ...router, // Mixin router methods (get, post, etc.)
49
+ use: (mw: any) => middlewares.push(mw),
50
+ listen: (port: number = options.port || 3000, callback?: () => void) => {
51
+ server.listen(port, options.host || '0.0.0.0', callback);
52
+ },
53
+ close: () => server.close()
54
+ };
55
+ }