@feasibleone/blong-gogo 1.7.3 → 1.8.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/CHANGELOG.md +15 -0
- package/bin/blong-gogo.sh +1 -1
- package/package.json +1 -1
- package/src/Gateway.ts +12 -5
- package/src/GatewayClient.ts +2 -2
- package/src/Registry.ts +8 -5
- package/src/Remote.ts +7 -0
- package/src/Resolution.ts +3 -3
- package/src/RpcClient.ts +0 -4
- package/src/RpcServer.ts +10 -2
- package/src/Watch.ts +45 -8
- package/src/codec/test/test/testCodecMle.ts +1 -1
- package/src/codec/test/test/testLoginTokenCreate.ts +1 -1
- package/src/layerProxy.ts +29 -15
- package/src/load.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.8.1](https://github.com/feasibleone/blong/compare/blong-gogo-v1.8.0...blong-gogo-v1.8.1) (2026-03-11)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
* schema generation and bin script ([df2ffa9](https://github.com/feasibleone/blong/commit/df2ffa9e1128d248f2a9c749b0559ea9fad09a26))
|
|
9
|
+
|
|
10
|
+
## [1.8.0](https://github.com/feasibleone/blong/compare/blong-gogo-v1.7.3...blong-gogo-v1.8.0) (2026-03-10)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* allow config override during start, improve types ([58c3840](https://github.com/feasibleone/blong/commit/58c3840b1543ce2d963c0bbdcb1a0712feface7c))
|
|
16
|
+
* support activation-based config in handler folder config.ts ([#64](https://github.com/feasibleone/blong/issues/64)) ([78e13ba](https://github.com/feasibleone/blong/commit/78e13baa0c85b05c8f31c8efa374f035e0009704))
|
|
17
|
+
|
|
3
18
|
## [1.7.3](https://github.com/feasibleone/blong/compare/blong-gogo-v1.7.2...blong-gogo-v1.7.3) (2026-03-07)
|
|
4
19
|
|
|
5
20
|
|
package/bin/blong-gogo.sh
CHANGED
package/package.json
CHANGED
package/src/Gateway.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
ApiSchema,
|
|
2
3
|
Errors,
|
|
3
4
|
GatewaySchema,
|
|
4
5
|
IErrorFactory,
|
|
@@ -68,7 +69,7 @@ interface IConfig extends IConfigMLE {
|
|
|
68
69
|
|
|
69
70
|
function operationParams(
|
|
70
71
|
operation: GatewaySchema['operation'],
|
|
71
|
-
bodySchema:
|
|
72
|
+
bodySchema: ApiSchema,
|
|
72
73
|
request: GatewayRequest,
|
|
73
74
|
): unknown {
|
|
74
75
|
const result =
|
|
@@ -108,10 +109,14 @@ function operationParams(
|
|
|
108
109
|
return result;
|
|
109
110
|
}, {}) ?? {};
|
|
110
111
|
if (
|
|
112
|
+
bodySchema &&
|
|
113
|
+
'type' in bodySchema &&
|
|
111
114
|
bodySchema?.type === 'object' &&
|
|
112
|
-
(bodySchema.properties ||
|
|
115
|
+
(bodySchema.properties ||
|
|
116
|
+
('additionalProperties' in bodySchema && bodySchema?.additionalProperties))
|
|
113
117
|
) {
|
|
114
|
-
if (
|
|
118
|
+
if ('additionalProperties' in bodySchema && bodySchema.additionalProperties)
|
|
119
|
+
Object.assign(result, request.body);
|
|
115
120
|
else if (bodySchema.properties)
|
|
116
121
|
Object.entries(bodySchema.properties).forEach(([name, value]) => {
|
|
117
122
|
if (name in (request.body as {})) result[snakeToCamel(name)] = request.body[name];
|
|
@@ -450,7 +455,7 @@ export default class Gateway extends Internal implements IGateway {
|
|
|
450
455
|
});
|
|
451
456
|
}
|
|
452
457
|
|
|
453
|
-
public async start(): Promise<
|
|
458
|
+
public async start(): Promise<IGateway> {
|
|
454
459
|
const old = this.#server;
|
|
455
460
|
try {
|
|
456
461
|
this.#server = fastify({
|
|
@@ -501,11 +506,13 @@ export default class Gateway extends Internal implements IGateway {
|
|
|
501
506
|
port: this.#config.port,
|
|
502
507
|
host: this.#config.host,
|
|
503
508
|
});
|
|
509
|
+
return this;
|
|
504
510
|
}
|
|
505
511
|
|
|
506
|
-
public async stop(): Promise<
|
|
512
|
+
public async stop(): Promise<IGateway> {
|
|
507
513
|
await this.#server?.close();
|
|
508
514
|
this.#server = null;
|
|
515
|
+
return this;
|
|
509
516
|
}
|
|
510
517
|
|
|
511
518
|
protected async restart(): Promise<void> {
|
package/src/GatewayClient.ts
CHANGED
|
@@ -5,8 +5,8 @@ import {spare} from 'ut-function.timing';
|
|
|
5
5
|
import Remote from './Remote.ts';
|
|
6
6
|
|
|
7
7
|
export interface IGatewayClient {
|
|
8
|
-
start: () => Promise<
|
|
9
|
-
stop: () => Promise<
|
|
8
|
+
start: () => Promise<IGatewayClient>;
|
|
9
|
+
stop: () => Promise<IGatewayClient>;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
interface IError extends Error {
|
package/src/Registry.ts
CHANGED
|
@@ -303,21 +303,23 @@ export default class Registry extends Internal implements IRegistry {
|
|
|
303
303
|
return {local, literals};
|
|
304
304
|
}
|
|
305
305
|
|
|
306
|
-
public async init(): Promise<
|
|
306
|
+
public async init(): Promise<IRegistry> {
|
|
307
307
|
for (const [id, {source, def}] of Object.entries(this.#config.api || {}))
|
|
308
308
|
await this.loadApi(id, def, source);
|
|
309
|
+
return this;
|
|
309
310
|
}
|
|
310
311
|
|
|
311
|
-
public async start(): Promise<
|
|
312
|
+
public async start(configOverride?: object): Promise<IRegistry> {
|
|
312
313
|
for (const id of Array.from(this.ports.keys())) await this.createPort(id);
|
|
313
|
-
for (const port of this.#ports.values()) await port.start();
|
|
314
|
+
for (const port of this.#ports.values()) await port.start(configOverride);
|
|
314
315
|
for (const port of this.#ports.values()) await port.ready();
|
|
315
316
|
this.#gateway?.route(await this._validations(), {name: '', version: ''});
|
|
316
317
|
await this.#resolution?.start();
|
|
317
318
|
await this.#rpcServer?.start();
|
|
318
319
|
await this.#remote?.start();
|
|
319
320
|
await this.#gateway?.start();
|
|
320
|
-
await this.#watch?.start(this, this.#remote);
|
|
321
|
+
await this.#watch?.start(this, this.#remote, configOverride);
|
|
322
|
+
return this;
|
|
321
323
|
// console.dir(this.ports.entries());
|
|
322
324
|
// console.dir(this.#validations);
|
|
323
325
|
// console.dir(created);
|
|
@@ -337,13 +339,14 @@ export default class Registry extends Internal implements IRegistry {
|
|
|
337
339
|
return await this.#watch.test(framework);
|
|
338
340
|
}
|
|
339
341
|
|
|
340
|
-
public async stop(): Promise<
|
|
342
|
+
public async stop(): Promise<IRegistry> {
|
|
341
343
|
await this.#watch?.stop();
|
|
342
344
|
await this.#gateway?.stop();
|
|
343
345
|
await this.#remote?.stop();
|
|
344
346
|
await this.#rpcServer?.stop();
|
|
345
347
|
await this.#resolution?.stop();
|
|
346
348
|
for (const port of this.#ports.values()) await port.stop();
|
|
349
|
+
return this;
|
|
347
350
|
}
|
|
348
351
|
|
|
349
352
|
public async loadApi(
|
package/src/Remote.ts
CHANGED
|
@@ -396,4 +396,11 @@ export default class Remote extends Internal implements IRemote {
|
|
|
396
396
|
meta: {mtid, method, url: httpRequest?.url, language, forward, cache},
|
|
397
397
|
};
|
|
398
398
|
}
|
|
399
|
+
|
|
400
|
+
public async stop(): Promise<IRemote> {
|
|
401
|
+
return this;
|
|
402
|
+
}
|
|
403
|
+
public async start(): Promise<IRemote> {
|
|
404
|
+
return this;
|
|
405
|
+
}
|
|
399
406
|
}
|
package/src/Resolution.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
export interface IResolution {
|
|
2
|
-
start: () => Promise<
|
|
3
|
-
stop: () => Promise<
|
|
2
|
+
start: () => Promise<unknown>;
|
|
3
|
+
stop: () => Promise<unknown>;
|
|
4
4
|
announce: (service: string, port: number) => void;
|
|
5
5
|
resolve: (
|
|
6
6
|
service: string,
|
|
7
7
|
invalidate: boolean,
|
|
8
|
-
namespace: string
|
|
8
|
+
namespace: string,
|
|
9
9
|
) => Promise<{
|
|
10
10
|
hostname: string;
|
|
11
11
|
port: string;
|
package/src/RpcClient.ts
CHANGED
package/src/RpcServer.ts
CHANGED
|
@@ -123,15 +123,23 @@ export default class RpcServer extends Internal implements IRpcServer {
|
|
|
123
123
|
methods.forEach(fn => this._unregister(namespace, fn));
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
public async start(): Promise<
|
|
126
|
+
public async start(): Promise<IRpcServer> {
|
|
127
127
|
this.#routes.forEach(route => this.#server.route(route));
|
|
128
128
|
await this.#server.listen({
|
|
129
129
|
port: this.#config.port,
|
|
130
130
|
host: this.#config.host,
|
|
131
131
|
});
|
|
132
|
+
return this;
|
|
132
133
|
}
|
|
133
134
|
|
|
134
|
-
public async stop(): Promise<
|
|
135
|
+
public async stop(): Promise<IRpcServer> {
|
|
135
136
|
await this.#server.close();
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
public info(): object {
|
|
141
|
+
return {
|
|
142
|
+
address: this.#server.server.address(),
|
|
143
|
+
};
|
|
136
144
|
}
|
|
137
145
|
}
|
package/src/Watch.ts
CHANGED
|
@@ -15,12 +15,13 @@ import {readFileSync, statSync, writeFileSync} from 'fs';
|
|
|
15
15
|
import {readdir} from 'fs/promises';
|
|
16
16
|
import {EventEmitter} from 'node:events';
|
|
17
17
|
import {basename, dirname, extname, join, relative, resolve} from 'path';
|
|
18
|
+
import merge from 'ut-function.merge';
|
|
18
19
|
|
|
19
20
|
import layerProxy from './layerProxy.ts';
|
|
20
21
|
import './watch.log.ts';
|
|
21
22
|
|
|
22
23
|
export interface IWatch {
|
|
23
|
-
start: (realm: IRegistry, remote: IRemote) => Promise<void>;
|
|
24
|
+
start: (realm: IRegistry, remote: IRemote, configOverride: object) => Promise<void>;
|
|
24
25
|
test: (tester: unknown) => Promise<void>;
|
|
25
26
|
stop: () => Promise<void>;
|
|
26
27
|
load: <T extends {result: unknown}>(
|
|
@@ -34,6 +35,7 @@ export interface IWatch {
|
|
|
34
35
|
const isCode = (filename: string): boolean => /(?<!\.d)\.m?(t|j)sx?$/i.test(filename);
|
|
35
36
|
const isLayerActivation = (filename: string): boolean =>
|
|
36
37
|
/^layer\.(server|browser)\.[mc]?[tj]sx?$/i.test(filename);
|
|
38
|
+
const isConfig = (filename: string): boolean => /^config\.[mc]?[tj]sx?$/i.test(filename);
|
|
37
39
|
const scan = async (...path: string[]): Promise<Dirent[]> =>
|
|
38
40
|
(await readdir(join(...path), {withFileTypes: true})).sort((a, b) =>
|
|
39
41
|
a < b ? -1 : a > b ? 1 : 0,
|
|
@@ -140,7 +142,9 @@ export default class Watch extends Internal implements IWatch {
|
|
|
140
142
|
/* eslint-disable @rushstack/typedef-var */
|
|
141
143
|
|
|
142
144
|
import {validationHandlers} from '@feasibleone/blong';
|
|
143
|
-
|
|
145
|
+
import { Type, type Static } from 'typebox';
|
|
146
|
+
|
|
147
|
+
${TypeScriptToTypeBox.Generate(schema.sort().join('\n'), {useTypeBoxImport: false}).trim()}
|
|
144
148
|
|
|
145
149
|
export default validationHandlers({
|
|
146
150
|
${names.sort().join(',\n')}
|
|
@@ -172,10 +176,39 @@ export default class Watch extends Internal implements IWatch {
|
|
|
172
176
|
const libs = [];
|
|
173
177
|
const handlerFilenames = [];
|
|
174
178
|
let latest = 0;
|
|
175
|
-
const
|
|
179
|
+
const allFiles = await scan(dir);
|
|
180
|
+
const configFile = allFiles.find(entry => entry.isFile() && isConfig(entry.name));
|
|
181
|
+
if (configFile) {
|
|
182
|
+
const configFilePath = join(dir, configFile.name);
|
|
183
|
+
const loaded = (
|
|
184
|
+
await import(
|
|
185
|
+
this.#config.enabled ? configFilePath + '?' + Date.now() : configFilePath
|
|
186
|
+
)
|
|
187
|
+
).default;
|
|
188
|
+
const folderName = basename(dir);
|
|
189
|
+
const mutableConfig = config as Record<string, unknown>;
|
|
190
|
+
const configNames = (mutableConfig.configNames as string[]) ?? [];
|
|
191
|
+
const folderConfig =
|
|
192
|
+
loaded &&
|
|
193
|
+
typeof loaded === 'object' &&
|
|
194
|
+
typeof loaded.default === 'object' &&
|
|
195
|
+
loaded.default !== null
|
|
196
|
+
? merge(
|
|
197
|
+
{},
|
|
198
|
+
...['default', ...configNames].map(name => loaded[name]).filter(Boolean),
|
|
199
|
+
)
|
|
200
|
+
: (loaded ?? {});
|
|
201
|
+
const namespaceOverride = mutableConfig?.namespace?.[folderName] ?? {};
|
|
202
|
+
mutableConfig[folderName] = merge({}, folderConfig, namespaceOverride);
|
|
203
|
+
}
|
|
204
|
+
const handlerFiles = allFiles
|
|
176
205
|
.sort()
|
|
177
206
|
.filter(
|
|
178
|
-
entry =>
|
|
207
|
+
entry =>
|
|
208
|
+
entry.isFile() &&
|
|
209
|
+
isCode(entry.name) &&
|
|
210
|
+
!isLayerActivation(entry.name) &&
|
|
211
|
+
!isConfig(entry.name),
|
|
179
212
|
);
|
|
180
213
|
await this.#apiSchema.generateDir(dir, handlerFiles);
|
|
181
214
|
for (const handlerEntry of handlerFiles) {
|
|
@@ -278,7 +311,7 @@ export default class Watch extends Internal implements IWatch {
|
|
|
278
311
|
}
|
|
279
312
|
}
|
|
280
313
|
|
|
281
|
-
private _watch(registry: IRegistry): void {
|
|
314
|
+
private _watch(registry: IRegistry, configOverride: object): void {
|
|
282
315
|
const fsWatcher = chokidar.watch(
|
|
283
316
|
Array.from(this.#handlerFolders.keys())
|
|
284
317
|
.map(folder => [
|
|
@@ -317,7 +350,7 @@ export default class Watch extends Internal implements IWatch {
|
|
|
317
350
|
registry.ports.set(layerConfig.name + '.' + id, item.port);
|
|
318
351
|
const port = await registry.createPort(layerConfig.name + '.' + id);
|
|
319
352
|
if (!port) return;
|
|
320
|
-
await port.start();
|
|
353
|
+
await port.start(configOverride);
|
|
321
354
|
await port.ready();
|
|
322
355
|
await registry.connected();
|
|
323
356
|
this.#emit.emit('test');
|
|
@@ -361,7 +394,11 @@ export default class Watch extends Internal implements IWatch {
|
|
|
361
394
|
});
|
|
362
395
|
}
|
|
363
396
|
|
|
364
|
-
public async start(
|
|
397
|
+
public async start(
|
|
398
|
+
registry: IRegistry,
|
|
399
|
+
remote: IRemote,
|
|
400
|
+
configOverride: object,
|
|
401
|
+
): Promise<void> {
|
|
365
402
|
this.log?.debug?.({
|
|
366
403
|
$meta: {mtid: 'event', method: 'watch.start'},
|
|
367
404
|
dir: Array.from(this.#handlerFolders.keys())
|
|
@@ -386,7 +423,7 @@ export default class Watch extends Internal implements IWatch {
|
|
|
386
423
|
done?.();
|
|
387
424
|
});
|
|
388
425
|
}
|
|
389
|
-
if (this.#config.enabled) this._watch(registry);
|
|
426
|
+
if (this.#config.enabled) this._watch(registry, configOverride);
|
|
390
427
|
|
|
391
428
|
await registry.connected();
|
|
392
429
|
}
|
|
@@ -4,7 +4,7 @@ import type Assert from 'node:assert';
|
|
|
4
4
|
const DAY = 24 * 60 * 60 * 1000;
|
|
5
5
|
export default handler(
|
|
6
6
|
({lib: {group}, handler: {testLoginTokenCreate, subjectAge, subjectTime}}) => ({
|
|
7
|
-
testCodecMle: ({name = 'Message Level Encryption'}, $meta) =>
|
|
7
|
+
testCodecMle: ({name = 'Message Level Encryption'}: {name?: string}, $meta) =>
|
|
8
8
|
group(name)([
|
|
9
9
|
testLoginTokenCreate({}, $meta),
|
|
10
10
|
async function mle(assert: typeof Assert, {$meta}: {$meta: IMeta}) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {handler, type IMeta} from '@feasibleone/blong';
|
|
2
2
|
|
|
3
3
|
export default handler(({lib: {group}, handler: {loginTokenCreate}}) => ({
|
|
4
|
-
testLoginTokenCreate: ({name = 'login token'}) =>
|
|
4
|
+
testLoginTokenCreate: ({name = 'login token'}: {name?: string}) =>
|
|
5
5
|
group(name)([
|
|
6
6
|
function login(assert: unknown, {$meta}: {$meta: IMeta}) {
|
|
7
7
|
console.log('create login token');
|
package/src/layerProxy.ts
CHANGED
|
@@ -55,17 +55,23 @@ export default function layerProxy(
|
|
|
55
55
|
);
|
|
56
56
|
ports.forEach(what => {
|
|
57
57
|
if (what.prototype instanceof port) {
|
|
58
|
-
where.port = async (
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
where.port = async (
|
|
59
|
+
{
|
|
60
|
+
id,
|
|
61
|
+
...portApi
|
|
62
|
+
}: Parameters<IAdapterFactory>[0] & {id: string},
|
|
63
|
+
configOverride: object,
|
|
64
|
+
) => {
|
|
65
|
+
const config = {
|
|
66
|
+
...moduleConfig?.[name],
|
|
67
|
+
id,
|
|
68
|
+
pkg: moduleConfig.pkg,
|
|
69
|
+
};
|
|
62
70
|
const port = new (what as IPort)({
|
|
63
71
|
...portApi,
|
|
64
|
-
config:
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
pkg: moduleConfig.pkg,
|
|
68
|
-
},
|
|
72
|
+
config: configOverride
|
|
73
|
+
? merge({}, config, configOverride)
|
|
74
|
+
: config,
|
|
69
75
|
configBase: moduleConfig.base,
|
|
70
76
|
});
|
|
71
77
|
await port.init();
|
|
@@ -73,10 +79,13 @@ export default function layerProxy(
|
|
|
73
79
|
};
|
|
74
80
|
where.port.config = moduleConfig?.[name];
|
|
75
81
|
} else if (['adapter', 'orchestrator'].includes(kind(what))) {
|
|
76
|
-
where.port = async (
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
where.port = async (
|
|
83
|
+
{
|
|
84
|
+
id,
|
|
85
|
+
...portApi
|
|
86
|
+
}: Parameters<IAdapterFactory>[0] & {id: string},
|
|
87
|
+
configOverride: object,
|
|
88
|
+
) => {
|
|
80
89
|
if (!id) return what(portApi);
|
|
81
90
|
const port = await createPort(
|
|
82
91
|
{
|
|
@@ -86,11 +95,16 @@ export default function layerProxy(
|
|
|
86
95
|
moduleConfig.base,
|
|
87
96
|
moduleConfig.configNames,
|
|
88
97
|
);
|
|
89
|
-
|
|
98
|
+
const config = {
|
|
90
99
|
...moduleConfig?.[name],
|
|
91
100
|
id,
|
|
92
101
|
pkg: moduleConfig.pkg,
|
|
93
|
-
}
|
|
102
|
+
};
|
|
103
|
+
await port.init(
|
|
104
|
+
configOverride
|
|
105
|
+
? merge({}, config, configOverride)
|
|
106
|
+
: config,
|
|
107
|
+
);
|
|
94
108
|
return port;
|
|
95
109
|
};
|
|
96
110
|
where.port.config = moduleConfig?.[name];
|
package/src/load.ts
CHANGED
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
type ILogger,
|
|
8
8
|
type IModuleConfig,
|
|
9
9
|
type IRegistry,
|
|
10
|
+
type Kind,
|
|
11
|
+
type Kinds,
|
|
10
12
|
type SolutionFactory,
|
|
11
13
|
} from '@feasibleone/blong';
|
|
12
14
|
import {existsSync} from 'fs';
|
|
@@ -108,7 +110,7 @@ async function loadConfig(config: string | object): Promise<object> {
|
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
export default async function loadRealm<T extends TSchema>(
|
|
111
|
-
def: SolutionFactory<T
|
|
113
|
+
def: SolutionFactory<T> & {[symbol: Kind]: Kinds},
|
|
112
114
|
name: string,
|
|
113
115
|
parentConfig: object | string,
|
|
114
116
|
configNames: string[],
|