@websolutespa/bom-mixer-store 0.0.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/.turbo/turbo-build.log +17 -0
- package/CHANGELOG.md +7 -0
- package/coverage/api/api.service.ts.html +559 -0
- package/coverage/api/api.store.ts.html +253 -0
- package/coverage/api/index.html +131 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/cobertura-coverage.xml +517 -0
- package/coverage/favicon.png +0 -0
- package/coverage/fs/fs.service.ts.html +466 -0
- package/coverage/fs/index.html +116 -0
- package/coverage/index.html +176 -0
- package/coverage/middleware/index.html +116 -0
- package/coverage/middleware/middleware.service.ts.html +340 -0
- package/coverage/mock/index.html +161 -0
- package/coverage/mock/mock.api.ts.html +118 -0
- package/coverage/mock/mock.interceptor.ts.html +133 -0
- package/coverage/mock/mock.service.ts.html +178 -0
- package/coverage/mock/mock.store.ts.html +175 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +196 -0
- package/coverage/store/index.html +131 -0
- package/coverage/store/store.service.ts.html +157 -0
- package/coverage/store/store.ts.html +172 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +2020 -0
- package/dist/index.mjs +1991 -0
- package/jest.config.ts +3 -0
- package/package.json +56 -0
- package/src/api/api.service.ts +158 -0
- package/src/api/api.store.ts +56 -0
- package/src/example.test.ts +8 -0
- package/src/fs/fs.service.ts +127 -0
- package/src/index.ts +9 -0
- package/src/middleware/middleware.service.ts +85 -0
- package/src/mock/mock.api.ts +11 -0
- package/src/mock/mock.interceptor.ts +16 -0
- package/src/mock/mock.service.ts +31 -0
- package/src/mock/mock.store.ts +30 -0
- package/src/store/store.service.ts +24 -0
- package/src/store/store.ts +29 -0
- package/tsconfig.json +16 -0
package/jest.config.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@websolutespa/bom-mixer-store",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"bom",
|
|
7
|
+
"mixer",
|
|
8
|
+
"store"
|
|
9
|
+
],
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git://github.com/websolutespa/bom.git"
|
|
14
|
+
},
|
|
15
|
+
"prepack": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"module": "./dist/index.mjs"
|
|
19
|
+
},
|
|
20
|
+
"postpack": {
|
|
21
|
+
"types": "./src/index.ts",
|
|
22
|
+
"main": "./src/index.ts"
|
|
23
|
+
},
|
|
24
|
+
"sideEffects": false,
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsup ./src/index.ts --format esm,cjs --dts --external react",
|
|
30
|
+
"lint": "eslint *.ts*",
|
|
31
|
+
"test": "jest",
|
|
32
|
+
"test-watch": "npm test -- --watch",
|
|
33
|
+
"coverage:integration": "npm run test -- --coverage",
|
|
34
|
+
"coverage": "run-s -c --silent coverage:*",
|
|
35
|
+
"coverage-open": "open-cli coverage/lcov-report/index.html",
|
|
36
|
+
"prepack": "bom prepack",
|
|
37
|
+
"postpack": "bom postpack"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@types/react": "^18.0.18",
|
|
42
|
+
"@types/react-dom": "^18.0.6",
|
|
43
|
+
"@websolutespa/bom-cli": "*",
|
|
44
|
+
"@websolutespa/test": "*",
|
|
45
|
+
"@websolutespa/tsconfig": "*",
|
|
46
|
+
"eslint": "^8.23.0",
|
|
47
|
+
"eslint-config-websolute": "*",
|
|
48
|
+
"npm-run-all": "^4.1.5",
|
|
49
|
+
"react": "^18.2.0",
|
|
50
|
+
"ts-node": "^10.9.1",
|
|
51
|
+
"typescript": "^4.8.2"
|
|
52
|
+
},
|
|
53
|
+
"types": "./dist/index.d.ts",
|
|
54
|
+
"main": "./dist/index.js",
|
|
55
|
+
"module": "./dist/index.mjs"
|
|
56
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { FetchRequestOptions, httpFetch, IEntity, IEquatable, IQuerable, merge, qsSerialize, QueryParams } from '@websolutespa/bom-mixer-core';
|
|
2
|
+
|
|
3
|
+
// !!! these keys are server-side only
|
|
4
|
+
const STORE_API_URL = process.env.STORE_URL;
|
|
5
|
+
const STORE_API_BASE = process.env.STORE_API;
|
|
6
|
+
const STORE_BEARER = process.env.STORE_BEARER;
|
|
7
|
+
|
|
8
|
+
const defaultStoreOptions: FetchRequestOptions = {
|
|
9
|
+
// mode: 'cors',
|
|
10
|
+
headers: {
|
|
11
|
+
Authorization: `Bearer ${STORE_BEARER}`,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export async function apiFetch(pathname: string, options: FetchRequestOptions = {}): Promise<any> {
|
|
16
|
+
const url = `${STORE_API_URL}${STORE_API_BASE}${pathname}`;
|
|
17
|
+
// console.log('ApiStore.apiFetch', url, options.method);
|
|
18
|
+
const apiOptions = merge({ ...defaultStoreOptions }, options);
|
|
19
|
+
const apiResponse = await httpFetch(url, apiOptions);
|
|
20
|
+
return apiResponse;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function apiGet(url: string, options: FetchRequestOptions = {}): Promise<any> {
|
|
24
|
+
return await apiFetch(url, { ...options, method: 'GET' });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function apiPost(url: string, payload: any, options: FetchRequestOptions = {}): Promise<any> {
|
|
28
|
+
return await apiFetch(url, { ...options, method: 'POST', body: JSON.stringify(payload) });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function apiPut(url: string, payload: any, options: FetchRequestOptions = {}): Promise<any> {
|
|
32
|
+
return await apiFetch(url, { ...options, method: 'PUT', body: JSON.stringify(payload) });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function apiPatch(url: string, payload: any, options: FetchRequestOptions = {}): Promise<any> {
|
|
36
|
+
return await apiFetch(url, { ...options, method: 'PATCH', body: JSON.stringify(payload) });
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function apiDelete(url: string, options: FetchRequestOptions = {}): Promise<any> {
|
|
40
|
+
return await apiFetch(url, { ...options, method: 'DELETE' });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export class ApiService<T extends IEntity> implements IQuerable<IEntity> {
|
|
44
|
+
key: string;
|
|
45
|
+
|
|
46
|
+
constructor(key: string) {
|
|
47
|
+
if (!key) {
|
|
48
|
+
throw new Error('ApiService: key is required');
|
|
49
|
+
}
|
|
50
|
+
this.key = key;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async findOne(params: QueryParams = {}): Promise<T | undefined> {
|
|
54
|
+
// const params = toFindParams(idOrParams);
|
|
55
|
+
// const search = this.search_(params);
|
|
56
|
+
params.pagination = params.pagination || false;
|
|
57
|
+
params.richText = params.richText || false;
|
|
58
|
+
const query = qsSerialize(params);
|
|
59
|
+
const search = query ? `?${query}` : '';
|
|
60
|
+
// const id = params.where.id as string | number;
|
|
61
|
+
// const url = `/${this.key}${id ? `/${encodeURIComponent(id)}` : ''}${search}`;
|
|
62
|
+
const url = `/${this.key}${search}`;
|
|
63
|
+
// console.log('ApiService', this.key, 'findOne', url);
|
|
64
|
+
const response = await apiGet(url);
|
|
65
|
+
const items: T[] = Array.isArray(response.docs) ? response.docs : response;
|
|
66
|
+
const item = items.length ? items[0] : undefined;
|
|
67
|
+
// console.log('ApiService.findOne', url);
|
|
68
|
+
// console.log('ApiService.findOne', url, item);
|
|
69
|
+
if (item) {
|
|
70
|
+
return this.decorator_(item, params);
|
|
71
|
+
}
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async findMany(params: QueryParams = {}): Promise<T[]> {
|
|
76
|
+
// const search = this.search_(params);
|
|
77
|
+
params.pagination = params.pagination || false;
|
|
78
|
+
params.richText = params.richText || false;
|
|
79
|
+
const query = qsSerialize(params);
|
|
80
|
+
const search = query ? `?${query}` : '';
|
|
81
|
+
const url = `/${this.key}${search}`;
|
|
82
|
+
const response = await apiGet(url);
|
|
83
|
+
const items: T[] = Array.isArray(response.docs) ? response.docs : response;
|
|
84
|
+
// console.log('ApiService.findMany', url);
|
|
85
|
+
// console.log('ApiService.findMany', url, items);
|
|
86
|
+
// items = this.where_(items, params);
|
|
87
|
+
return items.map(x => this.decorator_(x, params));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async create(payload: any): Promise<T> {
|
|
91
|
+
const url = `/${this.key}`;
|
|
92
|
+
// console.log('ApiService', this.key, 'create', url);
|
|
93
|
+
const item = await apiPost(url, payload);
|
|
94
|
+
return this.decorator_(item);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async update(payload: T): Promise<T> {
|
|
98
|
+
const url = `/${this.key}`;
|
|
99
|
+
// console.log('ApiService', this.key, 'update', url);
|
|
100
|
+
const item = await apiPut(url, payload);
|
|
101
|
+
return this.decorator_(item);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async delete(id: IEquatable) {
|
|
105
|
+
// const params = toFindParams(id);
|
|
106
|
+
const params = {
|
|
107
|
+
where: {
|
|
108
|
+
id: {
|
|
109
|
+
equals: id,
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
const query = qsSerialize(params);
|
|
114
|
+
const search = query ? `?${query}` : '';
|
|
115
|
+
// const search = this.search_(params);
|
|
116
|
+
const url = `/${this.key}${search}`;
|
|
117
|
+
// const url = `/${this.key}/${encodeURIComponent(id.toString())}`;
|
|
118
|
+
// console.log('ApiService', this.key, 'delete', url);
|
|
119
|
+
const item = await apiDelete(url);
|
|
120
|
+
return this.decorator_(item);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/*
|
|
124
|
+
protected where_(items: any[], params: FindParams): any[] {
|
|
125
|
+
const where = params.where;
|
|
126
|
+
if (where) {
|
|
127
|
+
const keys = Object.keys(where);
|
|
128
|
+
items = items.filter(x => {
|
|
129
|
+
return keys.reduce<boolean>((p, c) => {
|
|
130
|
+
return p && (x[c] === where[c]);
|
|
131
|
+
}, true);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
return items;
|
|
135
|
+
}
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
protected decorator_(item: any, params: QueryParams = {}): any {
|
|
139
|
+
return item;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/*
|
|
143
|
+
protected search_(params: QueryParams): string {
|
|
144
|
+
const search: { [key: string]: string; } = {};
|
|
145
|
+
Object.entries(params).forEach(([key, value]) => {
|
|
146
|
+
if (typeof value !== 'object') {
|
|
147
|
+
search[key] = value.toString();
|
|
148
|
+
} else {
|
|
149
|
+
switch (key) {
|
|
150
|
+
case 'where':
|
|
151
|
+
search[key] = JSON.stringify(value);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
return Object.keys(search).length ? '?' + new URLSearchParams(search).toString() : '';
|
|
156
|
+
}
|
|
157
|
+
*/
|
|
158
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { IEntity, IQuerable } from '@websolutespa/bom-mixer-core';
|
|
2
|
+
import { MockService } from '../mock/mock.service';
|
|
3
|
+
import { IStore } from '../store/store';
|
|
4
|
+
import { apiGet, ApiService } from './api.service';
|
|
5
|
+
|
|
6
|
+
export async function getApiStore<T extends IStore>(collections: string[]): Promise<T> {
|
|
7
|
+
// console.count('ApiStore.getApiStore');
|
|
8
|
+
// console.log('ApiStore.getApiStore', collections);
|
|
9
|
+
// console.log(process.env);
|
|
10
|
+
// console.log('NODE_ENV', process.env.NODE_ENV, 'NEXT_RUNTIME', process.env.NEXT_RUNTIME, 'NEXT_PHASE', process.env.NEXT_PHASE);
|
|
11
|
+
if (process.env.NEXT_PHASE === 'phase-production-build') {
|
|
12
|
+
return await getApiBuildStore();
|
|
13
|
+
} else {
|
|
14
|
+
return await getApiRuntimeStore(collections);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let RUNTIME_STORE_: IStore;
|
|
19
|
+
export async function getApiRuntimeStore<T extends IStore>(collections: string[]): Promise<T> {
|
|
20
|
+
if (RUNTIME_STORE_) {
|
|
21
|
+
return RUNTIME_STORE_ as T;
|
|
22
|
+
}
|
|
23
|
+
const store: { [key: string]: IQuerable<IEntity> } = {};
|
|
24
|
+
collections.sort();
|
|
25
|
+
collections.forEach(key => {
|
|
26
|
+
store[key] = new ApiService<any>(key);
|
|
27
|
+
});
|
|
28
|
+
// console.count('ApiStore.getApiRuntimeStore');
|
|
29
|
+
// console.log('StoreApi.getApiRuntimeStore', Object.keys(store));
|
|
30
|
+
RUNTIME_STORE_ = store;
|
|
31
|
+
return store as T;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let BUILD_STORE_: IStore;
|
|
35
|
+
export async function getApiBuildStore<T extends IStore>(): Promise<T> {
|
|
36
|
+
if (BUILD_STORE_) {
|
|
37
|
+
return BUILD_STORE_ as T;
|
|
38
|
+
}
|
|
39
|
+
const store: { [key: string]: IQuerable<IEntity> } = {};
|
|
40
|
+
const url = '/store?pagination=true&richText=false';
|
|
41
|
+
const json = await apiGet(url);
|
|
42
|
+
// const json: { [key: string]: { items: any[] } } = {};
|
|
43
|
+
if (json != null) {
|
|
44
|
+
const collections = Object.keys(json);
|
|
45
|
+
collections.sort();
|
|
46
|
+
collections.forEach(key => {
|
|
47
|
+
if (json[key]) {
|
|
48
|
+
store[key] = new MockService(json[key]);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// console.count('ApiStore.getApiBuildStore');
|
|
53
|
+
// console.log('StoreApi.getApiBuildStore', Object.keys(store));
|
|
54
|
+
BUILD_STORE_ = store;
|
|
55
|
+
return store as T;
|
|
56
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
|
|
5
|
+
export async function fsExists(pathname: string): Promise<boolean> {
|
|
6
|
+
try {
|
|
7
|
+
await fs.promises.access(pathname);
|
|
8
|
+
return true;
|
|
9
|
+
} catch {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function fsRead(pathname: string, encoding = 'utf8'): Promise<any> {
|
|
15
|
+
try {
|
|
16
|
+
const data = await fs.promises.readFile(pathname, encoding);
|
|
17
|
+
return data || null;
|
|
18
|
+
} catch (error) {
|
|
19
|
+
console.log('fsRead', error, pathname);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function fsReadJson(pathname: string): Promise<any> {
|
|
25
|
+
try {
|
|
26
|
+
const data = await fsRead(pathname);
|
|
27
|
+
if (data) {
|
|
28
|
+
return JSON.parse(data);
|
|
29
|
+
} else {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.log('fsReadJson', error, pathname);
|
|
34
|
+
// throw (error);
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function fsWrite(pathname: string, data: string, encoding = 'utf8'): Promise<void> {
|
|
40
|
+
try {
|
|
41
|
+
await fs.promises.mkdir(path.dirname(pathname), { recursive: true });
|
|
42
|
+
await fs.promises.writeFile(pathname, data, encoding);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
console.log('fsWrite', error, pathname);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export async function fsWriteJson(pathname: string, data: any): Promise<void> {
|
|
49
|
+
try {
|
|
50
|
+
await fsWrite(pathname, JSON.stringify(data, null, 2));
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.log('fsWriteJson', error, pathname);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function fsExistOrCreateFolder(pathname: string): Promise<void> {
|
|
57
|
+
try {
|
|
58
|
+
const exists = fs.existsSync(pathname);
|
|
59
|
+
if (!exists) {
|
|
60
|
+
await fs.promises.mkdir(pathname);
|
|
61
|
+
}
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.log('fsExistOrCreateFolder', error, pathname);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function fsReadDirectory(pathname: string, extension?: string): Promise<{ name: string, data: string }[]> {
|
|
68
|
+
const files = await fs.promises.readdir(pathname);
|
|
69
|
+
const items = files.flatMap(async (file: string) => {
|
|
70
|
+
const filePath = path.join(pathname, file);
|
|
71
|
+
const stat = await fs.promises.stat(filePath);
|
|
72
|
+
if (stat.isDirectory()) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (extension && path.parse(file).ext !== extension) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const data = await fs.promises.readFile(filePath);
|
|
79
|
+
if (!data) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
name: path.parse(file).name,
|
|
84
|
+
data: data.toString(),
|
|
85
|
+
};
|
|
86
|
+
}) as Promise<{ name: string, data: string }>[];
|
|
87
|
+
const items2 = (await Promise.all(items)).filter(Boolean);
|
|
88
|
+
return items2;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function fsReadJsonDirectory(pathname: string): Promise<{ name: string, data: any }[]> {
|
|
92
|
+
try {
|
|
93
|
+
const datas = await fsReadDirectory(pathname, '.json');
|
|
94
|
+
return datas.filter(Boolean).map(x => {
|
|
95
|
+
x.data = JSON.parse(x.data);
|
|
96
|
+
return x;
|
|
97
|
+
});
|
|
98
|
+
} catch (error) {
|
|
99
|
+
console.log('fsReadJsonDirectory', error, pathname);
|
|
100
|
+
// throw (error);
|
|
101
|
+
return [];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function fsWatch(pathname: string, callback: (eventType: any, fileName: string) => void, options?: { interval: 2000 }): void {
|
|
106
|
+
const debounced = debounce(callback);
|
|
107
|
+
return fs.watch(pathname, options, debounced);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function fsWatchFile(pathname: string, callback: (eventType: any, fileName: string) => void, options?: { interval: 2000 }): void {
|
|
111
|
+
const debounced = debounce(callback);
|
|
112
|
+
return fs.watchFile(pathname, options, debounced);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function pathJoin(...paths: string[]): string {
|
|
116
|
+
return path.join(process.cwd(), ...paths);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function debounce(callback: (...args: any[]) => void, timeout: number = 300) {
|
|
120
|
+
let timer: NodeJS.Timeout;
|
|
121
|
+
return (...args: any[]) => {
|
|
122
|
+
clearTimeout(timer);
|
|
123
|
+
timer = setTimeout(() => {
|
|
124
|
+
callback(...args);
|
|
125
|
+
}, timeout);
|
|
126
|
+
};
|
|
127
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './api/api.service';
|
|
2
|
+
export * from './api/api.store';
|
|
3
|
+
export * from './middleware/middleware.service';
|
|
4
|
+
export * from './mock/mock.api';
|
|
5
|
+
export * from './mock/mock.interceptor';
|
|
6
|
+
export * from './mock/mock.service';
|
|
7
|
+
export * from './mock/mock.store';
|
|
8
|
+
export * from './store/store';
|
|
9
|
+
export * from './store/store.service';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { NextRequest } from 'next/server';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
import { NextApiRequest, NextApiResponse } from 'next';
|
|
5
|
+
|
|
6
|
+
export type RouteMiddleware = (request: NextRequest, event: NextFetchEvent) => Promise<Response | undefined> | Response | undefined;
|
|
7
|
+
|
|
8
|
+
export type ApiMiddleware = (request: NextApiRequest, response: NextApiResponse) => unknown;
|
|
9
|
+
|
|
10
|
+
// Helper method to wait for a middleware to execute before continuing
|
|
11
|
+
// And to throw an error when an error happens in a middleware
|
|
12
|
+
export default function useApiMiddleware(middleware) {
|
|
13
|
+
return (request, response) => new Promise((resolve, reject) => {
|
|
14
|
+
middleware(request, response, (result) => {
|
|
15
|
+
if (result instanceof Error) {
|
|
16
|
+
return reject(result);
|
|
17
|
+
}
|
|
18
|
+
return resolve(result);
|
|
19
|
+
});
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
* @name withMiddleware
|
|
24
|
+
* @description combine multiple middleware before handling your API endpoint
|
|
25
|
+
* @param middlewares
|
|
26
|
+
export function withApiMiddleware(...middlewares: ApiMiddleware[]) {
|
|
27
|
+
|
|
28
|
+
return async function withMiddlewareHandler(request: NextApiRequest, response: NextApiResponse) {
|
|
29
|
+
|
|
30
|
+
async function evaluateHandler(middleware: ApiMiddleware, innerMiddleware?: ApiMiddleware | undefined) {
|
|
31
|
+
// return early when the request has
|
|
32
|
+
// been ended by a previous middleware
|
|
33
|
+
if (response.headersSent) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (typeof middleware === 'function') {
|
|
37
|
+
const handler = await middleware(request, response);
|
|
38
|
+
if (typeof handler === 'function') {
|
|
39
|
+
if (innerMiddleware) {
|
|
40
|
+
await handler(innerMiddleware);
|
|
41
|
+
const index = middlewares.indexOf(innerMiddleware);
|
|
42
|
+
// remove inner middleware
|
|
43
|
+
if (index >= 0) {
|
|
44
|
+
middlewares.splice(index, 1);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
await handler();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
for (let index = 0; index < middlewares.length; index++) {
|
|
53
|
+
const middleware = middlewares[index];
|
|
54
|
+
const nextMiddleware = middlewares[index + 1];
|
|
55
|
+
await evaluateHandler(middleware, nextMiddleware);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
export function isStaticRequest(request: NextRequest) {
|
|
62
|
+
const url = request.nextUrl;
|
|
63
|
+
const isStatic = url.pathname.indexOf('.') !== -1;
|
|
64
|
+
return isStatic;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function isNextRequest(request: NextRequest) {
|
|
68
|
+
const url = request.nextUrl;
|
|
69
|
+
const isNext = url.pathname.indexOf('/_next/') === 0;
|
|
70
|
+
return isNext;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function isApiRequest(request: NextRequest) {
|
|
74
|
+
const url = request.nextUrl;
|
|
75
|
+
const isApi = url.pathname.indexOf('/api') === 0;
|
|
76
|
+
return isApi;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function isExistingApiRoute(request: NextRequest) {
|
|
80
|
+
const rx = /^\/api\/(route|product|(product\/(.+))|(menu\/(.+)))\/?$/gm;
|
|
81
|
+
const url = request.nextUrl;
|
|
82
|
+
const isApi = url.pathname.indexOf('/api') === 0;
|
|
83
|
+
const isExisting = isApi && rx.test(url.pathname);
|
|
84
|
+
return isExisting;
|
|
85
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { fsRead, fsReadJson, pathJoin } from '../fs/fs.service';
|
|
2
|
+
|
|
3
|
+
export async function resolveMockApi(api: string): Promise<any> {
|
|
4
|
+
const pathname = pathJoin(`${api}.json`);
|
|
5
|
+
return await fsRead(pathname);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export async function parseMockApi(api: string): Promise<any> {
|
|
9
|
+
const pathname = pathJoin(`${api}.json`);
|
|
10
|
+
return await fsReadJson(pathname);
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NextFetchEvent, NextRequest, NextResponse } from 'next/server';
|
|
2
|
+
import { isExistingApiRoute } from '../middleware/middleware.service';
|
|
3
|
+
import { StoreStrategy, storeStrategy } from '../store/store';
|
|
4
|
+
|
|
5
|
+
export async function mockInterceptor(request: NextRequest, next: NextFetchEvent) {
|
|
6
|
+
if (storeStrategy !== StoreStrategy.Mock) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
if (!isExistingApiRoute(request)) {
|
|
10
|
+
const url = request.nextUrl.clone();
|
|
11
|
+
url.pathname = `/api/_mock/${encodeURIComponent(url.pathname)}`;
|
|
12
|
+
const response = NextResponse.rewrite(url);
|
|
13
|
+
return response;
|
|
14
|
+
}
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { IEntity, JsonService, localizeItem, QueryParams } from '@websolutespa/bom-mixer-core';
|
|
2
|
+
|
|
3
|
+
export class MockService<T extends IEntity = any> extends JsonService<T> {
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
findOne(params: QueryParams): Promise<T | undefined> {
|
|
7
|
+
const query = qsSerialize(params);
|
|
8
|
+
console.log('MockService.findOne', query);
|
|
9
|
+
const deserialized = qsDeserialize(query);
|
|
10
|
+
console.log('MockService.findOne', deserialized);
|
|
11
|
+
return super.findOne(params);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
findMany(params: QueryParams = {}): Promise<T[]> {
|
|
15
|
+
const query = qsSerialize(params);
|
|
16
|
+
console.log('MockService.findMany', query);
|
|
17
|
+
const deserialized = qsDeserialize(query);
|
|
18
|
+
console.log('MockService.findMany', deserialized);
|
|
19
|
+
return super.findMany(params);
|
|
20
|
+
}
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
protected override decorator_(item: any, params: QueryParams = {}): any {
|
|
24
|
+
if (params.locale) {
|
|
25
|
+
return localizeItem(item, params.locale);
|
|
26
|
+
} else {
|
|
27
|
+
return item;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { IEntity, IQuerable } from '@websolutespa/bom-mixer-core';
|
|
2
|
+
import { fsReadJson } from '../fs/fs.service';
|
|
3
|
+
import { IStore } from '../store/store';
|
|
4
|
+
import { MockService } from './mock.service';
|
|
5
|
+
|
|
6
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
7
|
+
const path = require('path');
|
|
8
|
+
|
|
9
|
+
let STORE_: IStore;
|
|
10
|
+
|
|
11
|
+
export async function getMockStore<T extends IStore>(): Promise<T> {
|
|
12
|
+
// console.log('getMockStore', STORE_ ? Object.keys(STORE_) : null);
|
|
13
|
+
// console.log('getMockStore.process.cwd', process.cwd());
|
|
14
|
+
if (STORE_) {
|
|
15
|
+
return STORE_ as T;
|
|
16
|
+
}
|
|
17
|
+
const pathname = path.join(process.cwd(), '.mixer', 'store.json');
|
|
18
|
+
// console.log('getMockStore.pathname', pathname);
|
|
19
|
+
const json = await fsReadJson(pathname);
|
|
20
|
+
const store: { [key: string]: IQuerable<IEntity> } = {};
|
|
21
|
+
if (json != null) {
|
|
22
|
+
Object.keys(json).forEach(key => {
|
|
23
|
+
store[key] = new MockService(json[key]);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
// console.count('MockStore.getMockStore');
|
|
27
|
+
// console.log('MockStore.getMockStore', Object.keys(store));
|
|
28
|
+
STORE_ = store;
|
|
29
|
+
return STORE_ as T;
|
|
30
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getApiStore } from '../api/api.store';
|
|
2
|
+
import { getMockStore } from '../mock/mock.store';
|
|
3
|
+
import { IStore, StoreStrategy, storeStrategy } from './store';
|
|
4
|
+
|
|
5
|
+
const CONFIG_: {
|
|
6
|
+
COLLECTIONS: string[],
|
|
7
|
+
} = {
|
|
8
|
+
COLLECTIONS: []
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function registerCollections(collections: string[] = []): void {
|
|
12
|
+
CONFIG_.COLLECTIONS = collections;
|
|
13
|
+
// console.log('registerCollections', collections);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function getStore<T extends IStore>(): Promise<T> {
|
|
17
|
+
// console.count('Store.getStore'); // !!! > 11000
|
|
18
|
+
switch (storeStrategy) {
|
|
19
|
+
case StoreStrategy.Api:
|
|
20
|
+
return await getApiStore<T>(CONFIG_.COLLECTIONS);
|
|
21
|
+
default:
|
|
22
|
+
return await getMockStore<T>();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { IEntity, IQuerable } from '@websolutespa/bom-mixer-core';
|
|
2
|
+
|
|
3
|
+
export type Store = {
|
|
4
|
+
[key: string]: IQuerable<IEntity>;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type MockStore = {
|
|
8
|
+
[key: string]: any[];
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export enum StoreStrategy {
|
|
12
|
+
Api = 'api',
|
|
13
|
+
Data = 'data',
|
|
14
|
+
Mock = 'mock',
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function getStoreStrategy(): StoreStrategy {
|
|
18
|
+
let storeStrategy = StoreStrategy.Mock;
|
|
19
|
+
if (process && process.env.STORE_STRATEGY) {
|
|
20
|
+
storeStrategy = process.env.STORE_STRATEGY as StoreStrategy;
|
|
21
|
+
}
|
|
22
|
+
return storeStrategy;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const storeStrategy: StoreStrategy = getStoreStrategy();
|
|
26
|
+
|
|
27
|
+
export type IStore = {
|
|
28
|
+
[key: string]: IQuerable<IEntity>;
|
|
29
|
+
};
|
package/tsconfig.json
ADDED