lupine.api 1.1.57 → 1.1.59
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 +3 -3
- package/admin/admin-about.tsx +12 -16
- package/admin/admin-config.tsx +47 -44
- package/admin/admin-css.tsx +3 -3
- package/admin/admin-db.tsx +75 -75
- package/admin/admin-frame-helper.tsx +364 -364
- package/admin/admin-frame.tsx +164 -164
- package/admin/admin-index.tsx +65 -65
- package/admin/admin-login.tsx +111 -111
- package/admin/admin-menu-edit.tsx +637 -637
- package/admin/admin-menu-list.tsx +87 -87
- package/admin/admin-page-edit.tsx +564 -564
- package/admin/admin-page-list.tsx +83 -83
- package/admin/admin-performance.tsx +28 -28
- package/admin/admin-release.tsx +427 -404
- package/admin/admin-resources.tsx +382 -382
- package/admin/admin-shell.tsx +89 -89
- package/admin/admin-table-data.tsx +146 -146
- package/admin/admin-table-list.tsx +230 -230
- package/admin/admin-test-animations.tsx +395 -395
- package/admin/admin-test-component.tsx +823 -808
- package/admin/admin-test-edit.tsx +319 -319
- package/admin/admin-test-themes.tsx +56 -56
- package/admin/admin-tokens.tsx +338 -338
- package/admin/design/admin-design.tsx +174 -174
- package/admin/design/block-grid.tsx +36 -36
- package/admin/design/block-grid1.tsx +21 -21
- package/admin/design/block-paragraph.tsx +19 -19
- package/admin/design/block-title.tsx +19 -19
- package/admin/design/design-block-box.tsx +140 -140
- package/admin/design/drag-data.tsx +24 -24
- package/admin/index.ts +9 -9
- package/admin/package.json +15 -15
- package/admin/tsconfig.json +127 -127
- package/dev/copy-folder.js +32 -32
- package/dev/cp-index-html.js +69 -69
- package/dev/file-utils.js +12 -12
- package/dev/index.js +18 -19
- package/dev/package.json +12 -12
- package/dev/plugin-ifelse.js +168 -168
- package/dev/plugin-ifelse.test.js +37 -37
- package/dev/run-cmd.js +14 -14
- package/dev/send-request.js +12 -12
- package/package.json +55 -55
- package/src/admin-api/admin-api-helper.ts +210 -205
- package/src/admin-api/admin-api.ts +65 -65
- package/src/admin-api/admin-auth.ts +152 -146
- package/src/admin-api/admin-config.ts +94 -84
- package/src/admin-api/admin-csv.ts +94 -94
- package/src/admin-api/admin-db.ts +269 -269
- package/src/admin-api/admin-menu.ts +135 -135
- package/src/admin-api/admin-page.ts +135 -135
- package/src/admin-api/admin-performance.ts +128 -128
- package/src/admin-api/admin-release.ts +703 -700
- package/src/admin-api/admin-resources.ts +318 -318
- package/src/admin-api/admin-token-helper.ts +82 -79
- package/src/admin-api/admin-tokens.ts +90 -90
- package/src/admin-api/index.ts +2 -2
- package/src/admin-api/web-config-api.ts +19 -19
- package/src/api/api-cache.ts +103 -103
- package/src/api/api-helper.ts +44 -44
- package/src/api/api-module.ts +67 -60
- package/src/api/api-router.ts +177 -177
- package/src/api/api-shared-storage.ts +64 -64
- package/src/api/async-storage.ts +5 -5
- package/src/api/debug-service.ts +56 -56
- package/src/api/encode-html.ts +27 -27
- package/src/api/handle-status.ts +75 -75
- package/src/api/index.ts +15 -16
- package/src/api/mini-web-socket.ts +270 -270
- package/src/api/server-content-type.ts +82 -82
- package/src/api/server-render.ts +235 -215
- package/src/api/shell-service.ts +74 -74
- package/src/api/simple-storage.ts +80 -80
- package/src/api/static-server.ts +128 -125
- package/src/api/to-client-delivery.ts +26 -26
- package/src/app/app-cache.ts +55 -55
- package/src/app/app-helper.ts +62 -62
- package/src/app/app-message.ts +109 -109
- package/src/app/app-shared-storage.ts +363 -363
- package/src/app/app-start.ts +136 -136
- package/src/app/cleanup-exit.ts +16 -16
- package/src/app/host-to-path.ts +38 -38
- package/src/app/index.ts +11 -11
- package/src/app/process-dev-requests.ts +130 -130
- package/src/app/web-listener.ts +294 -294
- package/src/app/web-processor.ts +47 -42
- package/src/app/web-server.ts +100 -100
- package/src/common-js/web-env.js +104 -104
- package/src/index.ts +7 -7
- package/src/lang/api-lang-en.ts +26 -26
- package/src/lang/api-lang-zh-cn.ts +27 -27
- package/src/lang/index.ts +2 -2
- package/src/lang/lang-helper.ts +76 -76
- package/src/lang/lang-props.ts +6 -6
- package/src/lib/db/db-helper.ts +23 -23
- package/src/lib/db/db-mysql.ts +249 -250
- package/src/lib/db/db-sqlite.ts +101 -101
- package/src/lib/db/db.spec.ts +28 -28
- package/src/lib/db/db.ts +325 -325
- package/src/lib/db/index.ts +5 -5
- package/src/lib/index.ts +3 -3
- package/src/lib/logger.spec.ts +214 -214
- package/src/lib/logger.ts +281 -281
- package/src/lib/runtime-require.ts +37 -37
- package/src/lib/utils/cookie-util.ts +34 -34
- package/src/lib/utils/crypto.ts +58 -58
- package/src/lib/utils/date-utils.ts +317 -317
- package/src/lib/utils/deep-merge.ts +37 -37
- package/src/lib/utils/delay.ts +12 -12
- package/src/lib/utils/file-setting.ts +55 -55
- package/src/lib/utils/format-bytes.ts +11 -11
- package/src/lib/utils/fs-utils.ts +158 -158
- package/src/lib/utils/get-env.ts +27 -27
- package/src/lib/utils/index.ts +12 -12
- package/src/lib/utils/is-type.ts +48 -48
- package/src/lib/utils/load-env.ts +14 -14
- package/src/lib/utils/pad.ts +6 -6
- package/src/models/api-base.ts +5 -5
- package/src/models/api-module-props.ts +10 -11
- package/src/models/api-router-props.ts +26 -26
- package/src/models/app-cache-props.ts +33 -33
- package/src/models/app-data-props.ts +10 -10
- package/src/models/app-helper-props.ts +6 -6
- package/src/models/app-shared-storage-props.ts +38 -38
- package/src/models/app-start-props.ts +18 -18
- package/src/models/async-storage-props.ts +13 -13
- package/src/models/db-config.ts +30 -30
- package/src/models/host-to-path-props.ts +12 -12
- package/src/models/index.ts +16 -16
- package/src/models/json-object.ts +8 -8
- package/src/models/locals-props.ts +36 -36
- package/src/models/logger-props.ts +84 -84
- package/src/models/simple-storage-props.ts +13 -14
- package/src/models/to-client-delivery-props.ts +6 -6
- package/tsconfig.json +115 -115
- package/dev/plugin-gen-versions.js +0 -20
|
@@ -1,363 +1,363 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A persistent storage to store data in primary process and share to all workers
|
|
3
|
-
*
|
|
4
|
-
* You should use apiStorage in api module
|
|
5
|
-
*/
|
|
6
|
-
import * as fs from 'fs/promises';
|
|
7
|
-
import * as path from 'path';
|
|
8
|
-
import cluster from 'cluster';
|
|
9
|
-
import { SimpleStorage } from '../api';
|
|
10
|
-
import { FsUtils, Logger } from '../lib';
|
|
11
|
-
import {
|
|
12
|
-
SimpleStorageDataProps,
|
|
13
|
-
AppSharedStorageApiPrefix,
|
|
14
|
-
AppSharedStorageMessageId,
|
|
15
|
-
AppSharedStorageWebPrefix,
|
|
16
|
-
IAppSharedStorage,
|
|
17
|
-
StorageMessageFromSubProcess,
|
|
18
|
-
} from '../models';
|
|
19
|
-
import { registerMessageHandlerFromPrimary, registerMessageHandlerFromWorker } from './app-message';
|
|
20
|
-
|
|
21
|
-
// in Api scope, use ApiSharedStorage instead of this
|
|
22
|
-
// storage cross clusters, loaded when start and saved before exist
|
|
23
|
-
export class AppSharedStorage implements IAppSharedStorage {
|
|
24
|
-
private static instance: IAppSharedStorage;
|
|
25
|
-
configMap: { [key: string]: { fPath: string; storage: SimpleStorage } } = {};
|
|
26
|
-
logger = new Logger('server-config');
|
|
27
|
-
|
|
28
|
-
private constructor() {}
|
|
29
|
-
|
|
30
|
-
public static getInstance(): IAppSharedStorage {
|
|
31
|
-
if (!AppSharedStorage.instance) {
|
|
32
|
-
AppSharedStorage.instance = new AppSharedStorage();
|
|
33
|
-
// register app message handlers
|
|
34
|
-
registerMessageHandlerFromPrimary(
|
|
35
|
-
AppSharedStorageMessageId,
|
|
36
|
-
AppSharedStorage.instance.messageFromPrimaryProcess.bind(AppSharedStorage.instance)
|
|
37
|
-
);
|
|
38
|
-
registerMessageHandlerFromWorker(
|
|
39
|
-
AppSharedStorageMessageId,
|
|
40
|
-
AppSharedStorage.instance.messageFromSubProcess.bind(AppSharedStorage.instance)
|
|
41
|
-
);
|
|
42
|
-
}
|
|
43
|
-
return AppSharedStorage.instance;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
private getWorker(workerId: number) {
|
|
47
|
-
for (let i in cluster.workers) {
|
|
48
|
-
if (cluster.workers[i]?.id === workerId) return cluster.workers[i];
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
private getStorageMap(appName: string) {
|
|
53
|
-
let storage = this.configMap[appName];
|
|
54
|
-
if (!storage) {
|
|
55
|
-
this.configMap[appName] = { fPath: '', storage: new SimpleStorage({}) };
|
|
56
|
-
storage = this.configMap[appName];
|
|
57
|
-
}
|
|
58
|
-
return storage;
|
|
59
|
-
}
|
|
60
|
-
private getStorage(appName: string): SimpleStorage {
|
|
61
|
-
return this.getStorageMap(appName).storage;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// this is primary, msg is from a client
|
|
65
|
-
messageFromSubProcess(msgObject: StorageMessageFromSubProcess) {
|
|
66
|
-
if (!cluster.isPrimary || !msgObject.action || !msgObject.key || !msgObject.appName || !msgObject.workerId) {
|
|
67
|
-
console.error('AppStorage got wrong message: ', msgObject);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (msgObject.action === 'get') {
|
|
72
|
-
const storage = this.getStorage(msgObject.appName);
|
|
73
|
-
const value = storage.get(msgObject.key, '');
|
|
74
|
-
// console.log(`AppStorage get value from ${msgObject.pid} in ${process.pid}, for key: ${msgObject.key}`);
|
|
75
|
-
// send message back to the worker from the worker id
|
|
76
|
-
const worker = this.getWorker(msgObject.workerId);
|
|
77
|
-
if (!worker) {
|
|
78
|
-
console.error("AppStorage can' find the worker: ", msgObject);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
worker.send({
|
|
82
|
-
value,
|
|
83
|
-
...msgObject,
|
|
84
|
-
});
|
|
85
|
-
} else if (msgObject.action === 'getWithPrefix') {
|
|
86
|
-
const storage = this.getStorage(msgObject.appName);
|
|
87
|
-
const value = storage.getWithPrefix(msgObject.key);
|
|
88
|
-
// console.log(`AppStorage getWithPrefix from ${msgObject.pid} in ${process.pid}, for key: ${msgObject.key}`);
|
|
89
|
-
// send message back to the worker from the worker id
|
|
90
|
-
const worker = this.getWorker(msgObject.workerId);
|
|
91
|
-
if (!worker) {
|
|
92
|
-
console.error("AppStorage can' find the worker: ", msgObject);
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
worker.send({
|
|
96
|
-
value: JSON.stringify(value),
|
|
97
|
-
...msgObject,
|
|
98
|
-
});
|
|
99
|
-
} else if (msgObject.action === 'set') {
|
|
100
|
-
const storage = this.getStorage(msgObject.appName);
|
|
101
|
-
storage.set(msgObject.key, msgObject.value);
|
|
102
|
-
} else if (msgObject.action === 'load') {
|
|
103
|
-
this.load(msgObject.appName, msgObject.rootPath || '');
|
|
104
|
-
} else if (msgObject.action === 'save') {
|
|
105
|
-
this.save(msgObject.appName);
|
|
106
|
-
} else {
|
|
107
|
-
this.logger.warn(`Unknown message: ${msgObject.action}`);
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// this is a worker and msg is from Primary
|
|
112
|
-
// when debug is on, it's in primary, but it shouldn't receive those msgs
|
|
113
|
-
// mainly for get (a worker requests a get, primaary sends the value back to here)
|
|
114
|
-
messageFromPrimaryProcess(msgObject: StorageMessageFromSubProcess) {
|
|
115
|
-
AppSharedStorageWorker.messageFromPrimaryProcess(msgObject);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// should be only called from primary when the app is starting
|
|
119
|
-
async load(appName: string, rootPath: string) {
|
|
120
|
-
if (!cluster.isPrimary) {
|
|
121
|
-
// throw new Error('AppStorage.load should be only called from primary');
|
|
122
|
-
await AppSharedStorageWorker.load(appName, rootPath);
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const map = this.getStorageMap(appName);
|
|
127
|
-
map.fPath = path.join(rootPath, 'config.json');
|
|
128
|
-
|
|
129
|
-
let tempPath = map.fPath;
|
|
130
|
-
try {
|
|
131
|
-
if (!(await FsUtils.pathExist(tempPath))) {
|
|
132
|
-
tempPath = path.join(rootPath, 'resources', 'config_default.json');
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
let json;
|
|
136
|
-
if ((json = await fs.readFile(tempPath, 'utf-8'))) {
|
|
137
|
-
// it will still save to map.fPath
|
|
138
|
-
map.storage.setContent(JSON.parse(json));
|
|
139
|
-
map.storage.Dirty = false;
|
|
140
|
-
this.logger.info(`Loading shared storage for [ ${appName} ] from ${tempPath}`);
|
|
141
|
-
}
|
|
142
|
-
} catch (e: any) {
|
|
143
|
-
this.logger.error('Loading json file failed: ' + tempPath, e.message);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// called from primary before exit, or from api to save changes
|
|
148
|
-
async save(appName?: string, exit?: boolean) {
|
|
149
|
-
if (!cluster.isPrimary) {
|
|
150
|
-
await AppSharedStorageWorker.save(appName);
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
console.log(`${process.pid} - AppStorage save, appName: ${appName}, exit: ${exit}`);
|
|
155
|
-
if (appName) {
|
|
156
|
-
const map = this.configMap[appName];
|
|
157
|
-
if (map && map.fPath && map.storage.size() > 0 && map.storage.Dirty) {
|
|
158
|
-
await map.storage.saveContent(map.fPath);
|
|
159
|
-
}
|
|
160
|
-
} else {
|
|
161
|
-
// save all data
|
|
162
|
-
for (let appName in this.configMap) {
|
|
163
|
-
const map = this.configMap[appName];
|
|
164
|
-
if (map && map.fPath && map.storage.size() > 0 && map.storage.Dirty) {
|
|
165
|
-
await map.storage.saveContent(map.fPath);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// this can be called in primary or worker
|
|
172
|
-
get(appName: string, key: string): Promise<string> {
|
|
173
|
-
return new Promise((resolve, reject) => {
|
|
174
|
-
// console.log(`${process.pid} - AppStorage get value for key: ${key}`);
|
|
175
|
-
|
|
176
|
-
if (!cluster.isPrimary) {
|
|
177
|
-
AppSharedStorageWorker.get(appName, key, resolve, reject);
|
|
178
|
-
return;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// in primary
|
|
182
|
-
resolve(this.getStorage(appName).get(key, ''));
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
getWeb(appName: string, key: string): Promise<string> {
|
|
186
|
-
return this.get(appName, AppSharedStorageWebPrefix + key);
|
|
187
|
-
}
|
|
188
|
-
getApi(appName: string, key: string): Promise<string> {
|
|
189
|
-
return this.get(appName, AppSharedStorageApiPrefix + key);
|
|
190
|
-
}
|
|
191
|
-
async getWebAll(appName: string): Promise<SimpleStorageDataProps> {
|
|
192
|
-
const webAll = await this.getWithPrefix(appName, AppSharedStorageWebPrefix);
|
|
193
|
-
|
|
194
|
-
const webSettingShortKey: SimpleStorageDataProps = {};
|
|
195
|
-
for (let item of Object.keys(webAll)) {
|
|
196
|
-
const newItem = item.substring(AppSharedStorageWebPrefix.length);
|
|
197
|
-
webSettingShortKey[newItem] = webAll[item];
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
return webSettingShortKey;
|
|
201
|
-
}
|
|
202
|
-
getWithPrefix(appName: string, prefixKey: string): Promise<SimpleStorageDataProps> {
|
|
203
|
-
return new Promise((resolve, reject) => {
|
|
204
|
-
// console.log(`${process.pid} - AppStorage getWithPrefix for prefixKey: ${prefixKey}`);
|
|
205
|
-
|
|
206
|
-
if (!cluster.isPrimary) {
|
|
207
|
-
AppSharedStorageWorker.getWithPrefix(appName, prefixKey, resolve, reject);
|
|
208
|
-
return;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
// in primary
|
|
212
|
-
resolve(this.getStorage(appName).getWithPrefix(prefixKey));
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// this can be called in primary or worker
|
|
217
|
-
set(appName: string, key: string, value: any) {
|
|
218
|
-
if (!cluster.isPrimary) {
|
|
219
|
-
AppSharedStorageWorker.set(appName, key, value);
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// in primary
|
|
224
|
-
this.getStorage(appName).set(key, value);
|
|
225
|
-
}
|
|
226
|
-
setWeb(appName: string, key: string, value: any) {
|
|
227
|
-
return this.set(appName, AppSharedStorageWebPrefix + key, value);
|
|
228
|
-
}
|
|
229
|
-
setApi(appName: string, key: string, value: any) {
|
|
230
|
-
return this.set(appName, AppSharedStorageApiPrefix + key, value);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
type AppSharedStorageWorkerMap = { [key: string]: any };
|
|
235
|
-
class AppSharedStorageWorker {
|
|
236
|
-
static handleMap: AppSharedStorageWorkerMap = {};
|
|
237
|
-
static logger = new Logger('server-config');
|
|
238
|
-
|
|
239
|
-
static messageFromPrimaryProcess(msgObject: StorageMessageFromSubProcess) {
|
|
240
|
-
if (cluster.isPrimary || !msgObject.action || !msgObject.key || !msgObject.uniqueKey) {
|
|
241
|
-
console.error('AppSharedStorageWorker got wrong message: ', msgObject);
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (msgObject.action === 'get') {
|
|
246
|
-
// console.log(`${process.pid} - AppStorage get value end for key: ${msgObject.key}`);
|
|
247
|
-
|
|
248
|
-
const value = msgObject.value;
|
|
249
|
-
// how to pass the value to the caller
|
|
250
|
-
const map = this.handleMap[msgObject.uniqueKey];
|
|
251
|
-
delete this.handleMap[msgObject.uniqueKey];
|
|
252
|
-
if (map) {
|
|
253
|
-
map.resolve(value);
|
|
254
|
-
} else {
|
|
255
|
-
throw new Error(`Unknown uniqueKey: ${msgObject.uniqueKey}`);
|
|
256
|
-
}
|
|
257
|
-
} else if (msgObject.action === 'getWithPrefix') {
|
|
258
|
-
console.log(`${process.pid} - AppStorage get value end for key: ${msgObject.key}`);
|
|
259
|
-
const value = JSON.parse(msgObject.value);
|
|
260
|
-
// how to pass the value to the caller
|
|
261
|
-
const map = this.handleMap[msgObject.uniqueKey];
|
|
262
|
-
delete this.handleMap[msgObject.uniqueKey];
|
|
263
|
-
if (map) {
|
|
264
|
-
map.resolve(value);
|
|
265
|
-
} else {
|
|
266
|
-
throw new Error(`Unknown uniqueKey: ${msgObject.uniqueKey}`);
|
|
267
|
-
}
|
|
268
|
-
} else {
|
|
269
|
-
this.logger.warn(`Unknown message: ${msgObject.action}`);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
static async load(appName: string, rootPath: string) {
|
|
274
|
-
if (cluster.isPrimary) {
|
|
275
|
-
throw new Error('AppSharedStorageWorker should be only called from workers');
|
|
276
|
-
}
|
|
277
|
-
const obj: StorageMessageFromSubProcess = {
|
|
278
|
-
id: AppSharedStorageMessageId,
|
|
279
|
-
pid: process.pid,
|
|
280
|
-
workerId: cluster.worker?.id || 0,
|
|
281
|
-
action: 'load',
|
|
282
|
-
appName: appName,
|
|
283
|
-
rootPath: rootPath,
|
|
284
|
-
key: 'load',
|
|
285
|
-
};
|
|
286
|
-
process.send!(obj);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
static async save(appName?: string) {
|
|
290
|
-
if (cluster.isPrimary) {
|
|
291
|
-
throw new Error('AppSharedStorageWorker should be only called from workers');
|
|
292
|
-
}
|
|
293
|
-
const obj: StorageMessageFromSubProcess = {
|
|
294
|
-
id: AppSharedStorageMessageId,
|
|
295
|
-
pid: process.pid,
|
|
296
|
-
workerId: cluster.worker?.id || 0,
|
|
297
|
-
action: 'save',
|
|
298
|
-
appName: appName || '',
|
|
299
|
-
key: 'save',
|
|
300
|
-
};
|
|
301
|
-
process.send!(obj);
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
static get(appName: string, key: string, resolve: (value: any) => void, reject: (reason: any) => void) {
|
|
305
|
-
if (cluster.isPrimary) {
|
|
306
|
-
throw new Error('AppSharedStorageWorker should be only called from workers');
|
|
307
|
-
}
|
|
308
|
-
const uniqueKey = key + ':' + crypto.randomUUID();
|
|
309
|
-
AppSharedStorageWorker.handleMap[uniqueKey] = { resolve, reject };
|
|
310
|
-
const obj: StorageMessageFromSubProcess = {
|
|
311
|
-
id: AppSharedStorageMessageId,
|
|
312
|
-
pid: process.pid,
|
|
313
|
-
workerId: cluster.worker?.id || 0,
|
|
314
|
-
action: 'get',
|
|
315
|
-
appName,
|
|
316
|
-
uniqueKey,
|
|
317
|
-
key,
|
|
318
|
-
};
|
|
319
|
-
process.send!(obj);
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
static getWithPrefix(
|
|
323
|
-
appName: string,
|
|
324
|
-
prefixKey: string,
|
|
325
|
-
resolve: (value: any) => void,
|
|
326
|
-
reject: (reason: any) => void
|
|
327
|
-
) {
|
|
328
|
-
if (cluster.isPrimary) {
|
|
329
|
-
throw new Error('AppSharedStorageWorker should be only called from workers');
|
|
330
|
-
}
|
|
331
|
-
const uniqueKey = prefixKey + ':' + crypto.randomUUID();
|
|
332
|
-
AppSharedStorageWorker.handleMap[uniqueKey] = { resolve, reject };
|
|
333
|
-
const obj: StorageMessageFromSubProcess = {
|
|
334
|
-
id: AppSharedStorageMessageId,
|
|
335
|
-
pid: process.pid,
|
|
336
|
-
workerId: cluster.worker?.id || 0,
|
|
337
|
-
action: 'getWithPrefix',
|
|
338
|
-
appName,
|
|
339
|
-
uniqueKey,
|
|
340
|
-
key: prefixKey,
|
|
341
|
-
};
|
|
342
|
-
process.send!(obj);
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
static set(appName: string, key: string, value: any) {
|
|
346
|
-
if (cluster.isPrimary) {
|
|
347
|
-
throw new Error('AppSharedStorageWorker should be only called from workers');
|
|
348
|
-
}
|
|
349
|
-
const obj: StorageMessageFromSubProcess = {
|
|
350
|
-
id: AppSharedStorageMessageId,
|
|
351
|
-
pid: process.pid,
|
|
352
|
-
workerId: cluster.worker?.id || 0,
|
|
353
|
-
action: 'set',
|
|
354
|
-
appName,
|
|
355
|
-
key,
|
|
356
|
-
value,
|
|
357
|
-
};
|
|
358
|
-
process.send!(obj);
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
// this can be used in app, but in api, it should use getAppStorage()
|
|
363
|
-
export const appStorage = /* @__PURE__ */ AppSharedStorage.getInstance();
|
|
1
|
+
/**
|
|
2
|
+
* A persistent storage to store data in primary process and share to all workers
|
|
3
|
+
*
|
|
4
|
+
* You should use apiStorage in api module
|
|
5
|
+
*/
|
|
6
|
+
import * as fs from 'fs/promises';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import cluster from 'cluster';
|
|
9
|
+
import { SimpleStorage } from '../api';
|
|
10
|
+
import { FsUtils, Logger } from '../lib';
|
|
11
|
+
import {
|
|
12
|
+
SimpleStorageDataProps,
|
|
13
|
+
AppSharedStorageApiPrefix,
|
|
14
|
+
AppSharedStorageMessageId,
|
|
15
|
+
AppSharedStorageWebPrefix,
|
|
16
|
+
IAppSharedStorage,
|
|
17
|
+
StorageMessageFromSubProcess,
|
|
18
|
+
} from '../models';
|
|
19
|
+
import { registerMessageHandlerFromPrimary, registerMessageHandlerFromWorker } from './app-message';
|
|
20
|
+
|
|
21
|
+
// in Api scope, use ApiSharedStorage instead of this
|
|
22
|
+
// storage cross clusters, loaded when start and saved before exist
|
|
23
|
+
export class AppSharedStorage implements IAppSharedStorage {
|
|
24
|
+
private static instance: IAppSharedStorage;
|
|
25
|
+
configMap: { [key: string]: { fPath: string; storage: SimpleStorage } } = {};
|
|
26
|
+
logger = new Logger('server-config');
|
|
27
|
+
|
|
28
|
+
private constructor() {}
|
|
29
|
+
|
|
30
|
+
public static getInstance(): IAppSharedStorage {
|
|
31
|
+
if (!AppSharedStorage.instance) {
|
|
32
|
+
AppSharedStorage.instance = new AppSharedStorage();
|
|
33
|
+
// register app message handlers
|
|
34
|
+
registerMessageHandlerFromPrimary(
|
|
35
|
+
AppSharedStorageMessageId,
|
|
36
|
+
AppSharedStorage.instance.messageFromPrimaryProcess.bind(AppSharedStorage.instance)
|
|
37
|
+
);
|
|
38
|
+
registerMessageHandlerFromWorker(
|
|
39
|
+
AppSharedStorageMessageId,
|
|
40
|
+
AppSharedStorage.instance.messageFromSubProcess.bind(AppSharedStorage.instance)
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return AppSharedStorage.instance;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private getWorker(workerId: number) {
|
|
47
|
+
for (let i in cluster.workers) {
|
|
48
|
+
if (cluster.workers[i]?.id === workerId) return cluster.workers[i];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private getStorageMap(appName: string) {
|
|
53
|
+
let storage = this.configMap[appName];
|
|
54
|
+
if (!storage) {
|
|
55
|
+
this.configMap[appName] = { fPath: '', storage: new SimpleStorage({}) };
|
|
56
|
+
storage = this.configMap[appName];
|
|
57
|
+
}
|
|
58
|
+
return storage;
|
|
59
|
+
}
|
|
60
|
+
private getStorage(appName: string): SimpleStorage {
|
|
61
|
+
return this.getStorageMap(appName).storage;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// this is primary, msg is from a client
|
|
65
|
+
messageFromSubProcess(msgObject: StorageMessageFromSubProcess) {
|
|
66
|
+
if (!cluster.isPrimary || !msgObject.action || !msgObject.key || !msgObject.appName || !msgObject.workerId) {
|
|
67
|
+
console.error('AppStorage got wrong message: ', msgObject);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (msgObject.action === 'get') {
|
|
72
|
+
const storage = this.getStorage(msgObject.appName);
|
|
73
|
+
const value = storage.get(msgObject.key, '');
|
|
74
|
+
// console.log(`AppStorage get value from ${msgObject.pid} in ${process.pid}, for key: ${msgObject.key}`);
|
|
75
|
+
// send message back to the worker from the worker id
|
|
76
|
+
const worker = this.getWorker(msgObject.workerId);
|
|
77
|
+
if (!worker) {
|
|
78
|
+
console.error("AppStorage can' find the worker: ", msgObject);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
worker.send({
|
|
82
|
+
value,
|
|
83
|
+
...msgObject,
|
|
84
|
+
});
|
|
85
|
+
} else if (msgObject.action === 'getWithPrefix') {
|
|
86
|
+
const storage = this.getStorage(msgObject.appName);
|
|
87
|
+
const value = storage.getWithPrefix(msgObject.key);
|
|
88
|
+
// console.log(`AppStorage getWithPrefix from ${msgObject.pid} in ${process.pid}, for key: ${msgObject.key}`);
|
|
89
|
+
// send message back to the worker from the worker id
|
|
90
|
+
const worker = this.getWorker(msgObject.workerId);
|
|
91
|
+
if (!worker) {
|
|
92
|
+
console.error("AppStorage can' find the worker: ", msgObject);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
worker.send({
|
|
96
|
+
value: JSON.stringify(value),
|
|
97
|
+
...msgObject,
|
|
98
|
+
});
|
|
99
|
+
} else if (msgObject.action === 'set') {
|
|
100
|
+
const storage = this.getStorage(msgObject.appName);
|
|
101
|
+
storage.set(msgObject.key, msgObject.value);
|
|
102
|
+
} else if (msgObject.action === 'load') {
|
|
103
|
+
this.load(msgObject.appName, msgObject.rootPath || '');
|
|
104
|
+
} else if (msgObject.action === 'save') {
|
|
105
|
+
this.save(msgObject.appName);
|
|
106
|
+
} else {
|
|
107
|
+
this.logger.warn(`Unknown message: ${msgObject.action}`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// this is a worker and msg is from Primary
|
|
112
|
+
// when debug is on, it's in primary, but it shouldn't receive those msgs
|
|
113
|
+
// mainly for get (a worker requests a get, primaary sends the value back to here)
|
|
114
|
+
messageFromPrimaryProcess(msgObject: StorageMessageFromSubProcess) {
|
|
115
|
+
AppSharedStorageWorker.messageFromPrimaryProcess(msgObject);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// should be only called from primary when the app is starting
|
|
119
|
+
async load(appName: string, rootPath: string) {
|
|
120
|
+
if (!cluster.isPrimary) {
|
|
121
|
+
// throw new Error('AppStorage.load should be only called from primary');
|
|
122
|
+
await AppSharedStorageWorker.load(appName, rootPath);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const map = this.getStorageMap(appName);
|
|
127
|
+
map.fPath = path.join(rootPath, 'config.json');
|
|
128
|
+
|
|
129
|
+
let tempPath = map.fPath;
|
|
130
|
+
try {
|
|
131
|
+
if (!(await FsUtils.pathExist(tempPath))) {
|
|
132
|
+
tempPath = path.join(rootPath, 'resources', 'config_default.json');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
let json;
|
|
136
|
+
if ((json = await fs.readFile(tempPath, 'utf-8'))) {
|
|
137
|
+
// it will still save to map.fPath
|
|
138
|
+
map.storage.setContent(JSON.parse(json));
|
|
139
|
+
map.storage.Dirty = false;
|
|
140
|
+
this.logger.info(`Loading shared storage for [ ${appName} ] from ${tempPath}`);
|
|
141
|
+
}
|
|
142
|
+
} catch (e: any) {
|
|
143
|
+
this.logger.error('Loading json file failed: ' + tempPath, e.message);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// called from primary before exit, or from api to save changes
|
|
148
|
+
async save(appName?: string, exit?: boolean) {
|
|
149
|
+
if (!cluster.isPrimary) {
|
|
150
|
+
await AppSharedStorageWorker.save(appName);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
console.log(`${process.pid} - AppStorage save, appName: ${appName}, exit: ${exit}`);
|
|
155
|
+
if (appName) {
|
|
156
|
+
const map = this.configMap[appName];
|
|
157
|
+
if (map && map.fPath && map.storage.size() > 0 && map.storage.Dirty) {
|
|
158
|
+
await map.storage.saveContent(map.fPath);
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
// save all data
|
|
162
|
+
for (let appName in this.configMap) {
|
|
163
|
+
const map = this.configMap[appName];
|
|
164
|
+
if (map && map.fPath && map.storage.size() > 0 && map.storage.Dirty) {
|
|
165
|
+
await map.storage.saveContent(map.fPath);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// this can be called in primary or worker
|
|
172
|
+
get(appName: string, key: string): Promise<string> {
|
|
173
|
+
return new Promise((resolve, reject) => {
|
|
174
|
+
// console.log(`${process.pid} - AppStorage get value for key: ${key}`);
|
|
175
|
+
|
|
176
|
+
if (!cluster.isPrimary) {
|
|
177
|
+
AppSharedStorageWorker.get(appName, key, resolve, reject);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// in primary
|
|
182
|
+
resolve(this.getStorage(appName).get(key, ''));
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
getWeb(appName: string, key: string): Promise<string> {
|
|
186
|
+
return this.get(appName, AppSharedStorageWebPrefix + key);
|
|
187
|
+
}
|
|
188
|
+
getApi(appName: string, key: string): Promise<string> {
|
|
189
|
+
return this.get(appName, AppSharedStorageApiPrefix + key);
|
|
190
|
+
}
|
|
191
|
+
async getWebAll(appName: string): Promise<SimpleStorageDataProps> {
|
|
192
|
+
const webAll = await this.getWithPrefix(appName, AppSharedStorageWebPrefix);
|
|
193
|
+
|
|
194
|
+
const webSettingShortKey: SimpleStorageDataProps = {};
|
|
195
|
+
for (let item of Object.keys(webAll)) {
|
|
196
|
+
const newItem = item.substring(AppSharedStorageWebPrefix.length);
|
|
197
|
+
webSettingShortKey[newItem] = webAll[item];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return webSettingShortKey;
|
|
201
|
+
}
|
|
202
|
+
getWithPrefix(appName: string, prefixKey: string): Promise<SimpleStorageDataProps> {
|
|
203
|
+
return new Promise((resolve, reject) => {
|
|
204
|
+
// console.log(`${process.pid} - AppStorage getWithPrefix for prefixKey: ${prefixKey}`);
|
|
205
|
+
|
|
206
|
+
if (!cluster.isPrimary) {
|
|
207
|
+
AppSharedStorageWorker.getWithPrefix(appName, prefixKey, resolve, reject);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// in primary
|
|
212
|
+
resolve(this.getStorage(appName).getWithPrefix(prefixKey));
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// this can be called in primary or worker
|
|
217
|
+
set(appName: string, key: string, value: any) {
|
|
218
|
+
if (!cluster.isPrimary) {
|
|
219
|
+
AppSharedStorageWorker.set(appName, key, value);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// in primary
|
|
224
|
+
this.getStorage(appName).set(key, value);
|
|
225
|
+
}
|
|
226
|
+
setWeb(appName: string, key: string, value: any) {
|
|
227
|
+
return this.set(appName, AppSharedStorageWebPrefix + key, value);
|
|
228
|
+
}
|
|
229
|
+
setApi(appName: string, key: string, value: any) {
|
|
230
|
+
return this.set(appName, AppSharedStorageApiPrefix + key, value);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
type AppSharedStorageWorkerMap = { [key: string]: any };
|
|
235
|
+
class AppSharedStorageWorker {
|
|
236
|
+
static handleMap: AppSharedStorageWorkerMap = {};
|
|
237
|
+
static logger = new Logger('server-config');
|
|
238
|
+
|
|
239
|
+
static messageFromPrimaryProcess(msgObject: StorageMessageFromSubProcess) {
|
|
240
|
+
if (cluster.isPrimary || !msgObject.action || !msgObject.key || !msgObject.uniqueKey) {
|
|
241
|
+
console.error('AppSharedStorageWorker got wrong message: ', msgObject);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (msgObject.action === 'get') {
|
|
246
|
+
// console.log(`${process.pid} - AppStorage get value end for key: ${msgObject.key}`);
|
|
247
|
+
|
|
248
|
+
const value = msgObject.value;
|
|
249
|
+
// how to pass the value to the caller
|
|
250
|
+
const map = this.handleMap[msgObject.uniqueKey];
|
|
251
|
+
delete this.handleMap[msgObject.uniqueKey];
|
|
252
|
+
if (map) {
|
|
253
|
+
map.resolve(value);
|
|
254
|
+
} else {
|
|
255
|
+
throw new Error(`Unknown uniqueKey: ${msgObject.uniqueKey}`);
|
|
256
|
+
}
|
|
257
|
+
} else if (msgObject.action === 'getWithPrefix') {
|
|
258
|
+
console.log(`${process.pid} - AppStorage get value end for key: ${msgObject.key}`);
|
|
259
|
+
const value = JSON.parse(msgObject.value);
|
|
260
|
+
// how to pass the value to the caller
|
|
261
|
+
const map = this.handleMap[msgObject.uniqueKey];
|
|
262
|
+
delete this.handleMap[msgObject.uniqueKey];
|
|
263
|
+
if (map) {
|
|
264
|
+
map.resolve(value);
|
|
265
|
+
} else {
|
|
266
|
+
throw new Error(`Unknown uniqueKey: ${msgObject.uniqueKey}`);
|
|
267
|
+
}
|
|
268
|
+
} else {
|
|
269
|
+
this.logger.warn(`Unknown message: ${msgObject.action}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
static async load(appName: string, rootPath: string) {
|
|
274
|
+
if (cluster.isPrimary) {
|
|
275
|
+
throw new Error('AppSharedStorageWorker should be only called from workers');
|
|
276
|
+
}
|
|
277
|
+
const obj: StorageMessageFromSubProcess = {
|
|
278
|
+
id: AppSharedStorageMessageId,
|
|
279
|
+
pid: process.pid,
|
|
280
|
+
workerId: cluster.worker?.id || 0,
|
|
281
|
+
action: 'load',
|
|
282
|
+
appName: appName,
|
|
283
|
+
rootPath: rootPath,
|
|
284
|
+
key: 'load',
|
|
285
|
+
};
|
|
286
|
+
process.send!(obj);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
static async save(appName?: string) {
|
|
290
|
+
if (cluster.isPrimary) {
|
|
291
|
+
throw new Error('AppSharedStorageWorker should be only called from workers');
|
|
292
|
+
}
|
|
293
|
+
const obj: StorageMessageFromSubProcess = {
|
|
294
|
+
id: AppSharedStorageMessageId,
|
|
295
|
+
pid: process.pid,
|
|
296
|
+
workerId: cluster.worker?.id || 0,
|
|
297
|
+
action: 'save',
|
|
298
|
+
appName: appName || '',
|
|
299
|
+
key: 'save',
|
|
300
|
+
};
|
|
301
|
+
process.send!(obj);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
static get(appName: string, key: string, resolve: (value: any) => void, reject: (reason: any) => void) {
|
|
305
|
+
if (cluster.isPrimary) {
|
|
306
|
+
throw new Error('AppSharedStorageWorker should be only called from workers');
|
|
307
|
+
}
|
|
308
|
+
const uniqueKey = key + ':' + crypto.randomUUID();
|
|
309
|
+
AppSharedStorageWorker.handleMap[uniqueKey] = { resolve, reject };
|
|
310
|
+
const obj: StorageMessageFromSubProcess = {
|
|
311
|
+
id: AppSharedStorageMessageId,
|
|
312
|
+
pid: process.pid,
|
|
313
|
+
workerId: cluster.worker?.id || 0,
|
|
314
|
+
action: 'get',
|
|
315
|
+
appName,
|
|
316
|
+
uniqueKey,
|
|
317
|
+
key,
|
|
318
|
+
};
|
|
319
|
+
process.send!(obj);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
static getWithPrefix(
|
|
323
|
+
appName: string,
|
|
324
|
+
prefixKey: string,
|
|
325
|
+
resolve: (value: any) => void,
|
|
326
|
+
reject: (reason: any) => void
|
|
327
|
+
) {
|
|
328
|
+
if (cluster.isPrimary) {
|
|
329
|
+
throw new Error('AppSharedStorageWorker should be only called from workers');
|
|
330
|
+
}
|
|
331
|
+
const uniqueKey = prefixKey + ':' + crypto.randomUUID();
|
|
332
|
+
AppSharedStorageWorker.handleMap[uniqueKey] = { resolve, reject };
|
|
333
|
+
const obj: StorageMessageFromSubProcess = {
|
|
334
|
+
id: AppSharedStorageMessageId,
|
|
335
|
+
pid: process.pid,
|
|
336
|
+
workerId: cluster.worker?.id || 0,
|
|
337
|
+
action: 'getWithPrefix',
|
|
338
|
+
appName,
|
|
339
|
+
uniqueKey,
|
|
340
|
+
key: prefixKey,
|
|
341
|
+
};
|
|
342
|
+
process.send!(obj);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
static set(appName: string, key: string, value: any) {
|
|
346
|
+
if (cluster.isPrimary) {
|
|
347
|
+
throw new Error('AppSharedStorageWorker should be only called from workers');
|
|
348
|
+
}
|
|
349
|
+
const obj: StorageMessageFromSubProcess = {
|
|
350
|
+
id: AppSharedStorageMessageId,
|
|
351
|
+
pid: process.pid,
|
|
352
|
+
workerId: cluster.worker?.id || 0,
|
|
353
|
+
action: 'set',
|
|
354
|
+
appName,
|
|
355
|
+
key,
|
|
356
|
+
value,
|
|
357
|
+
};
|
|
358
|
+
process.send!(obj);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// this can be used in app, but in api, it should use getAppStorage()
|
|
363
|
+
export const appStorage = /* @__PURE__ */ AppSharedStorage.getInstance();
|