@feasibleone/blong-gogo 1.7.2 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.8.0](https://github.com/feasibleone/blong/compare/blong-gogo-v1.7.3...blong-gogo-v1.8.0) (2026-03-10)
4
+
5
+
6
+ ### Features
7
+
8
+ * allow config override during start, improve types ([58c3840](https://github.com/feasibleone/blong/commit/58c3840b1543ce2d963c0bbdcb1a0712feface7c))
9
+ * 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))
10
+
11
+ ## [1.7.3](https://github.com/feasibleone/blong/compare/blong-gogo-v1.7.2...blong-gogo-v1.7.3) (2026-03-07)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * bin script ([e26c9ee](https://github.com/feasibleone/blong/commit/e26c9eef1785b594c3afddddb7d4b8d57be93ae2))
17
+
3
18
  ## [1.7.2](https://github.com/feasibleone/blong/compare/blong-gogo-v1.7.1...blong-gogo-v1.7.2) (2026-03-07)
4
19
 
5
20
 
@@ -0,0 +1,13 @@
1
+ #!/bin/bash
2
+
3
+ exists=$(npm ls -g @feasibleone/blong-gogo --parseable)
4
+ if [[ -z "$exists" ]]; then
5
+ echo "Installing @feasibleone/blong-gogo globally..."
6
+ npm i -g @feasibleone/blong-gogo
7
+ else
8
+ echo "@feasibleone/blong-gogo is already installed globally."
9
+ fi
10
+ blong_gogo=$(npm ls -g @feasibleone/blong-gogo --parseable)
11
+ mv -f "$blong_gogo" ./
12
+ cp -r blong-gogo/bin/blong.sh "$blong_gogo"/bin/
13
+ ./blong-gogo/bin/blong.ts
package/package.json CHANGED
@@ -1,8 +1,17 @@
1
1
  {
2
+ "name": "@feasibleone/blong-gogo",
3
+ "version": "1.8.0",
4
+ "repository": {
5
+ "url": "git+https://github.com/feasibleone/blong.git"
6
+ },
7
+ "type": "module",
8
+ "exports": {
9
+ ".": "./src/load.ts"
10
+ },
2
11
  "bin": {
3
12
  "blong": "./bin/blong.ts",
4
- "blong-gogo": "./bin/blong.ts",
5
- "blong-dev": "./bin/blong-dev.ts"
13
+ "blong-dev": "./bin/blong-dev.ts",
14
+ "blong-gogo": "./bin/blong-gogo.sh"
6
15
  },
7
16
  "dependencies": {
8
17
  "@apidevtools/swagger-parser": "^12.1.0",
@@ -19,15 +28,14 @@
19
28
  "@keycloak/keycloak-admin-client": "^26.5.4",
20
29
  "@kubernetes/client-node": "^1.4.0",
21
30
  "@octokit/rest": "^22.0.1",
22
- "typebox": "^1.1.5",
23
- "@sinclair/typebox-codegen": "^0.10.4",
31
+ "@sinclair/typebox-codegen": "^0.11.1",
24
32
  "@slack/webhook": "^7.0.7",
25
33
  "ajv-formats": "^3.0.1",
26
34
  "browser-process-hrtime": "^1.0.0",
27
35
  "chokidar": "^5.0.0",
28
36
  "fastify": "^5.8.2",
29
37
  "fastify-plugin": "^5.1.0",
30
- "glob": "^13.0.3",
38
+ "glob": "^13.0.6",
31
39
  "got": "^14.6.6",
32
40
  "jose": "^5.7.0",
33
41
  "knex": "^3.1.0",
@@ -44,6 +52,8 @@
44
52
  "pino": "^10.3.1",
45
53
  "pino-pretty": "^13.1.3",
46
54
  "reconnect-core": "^1.3.0",
55
+ "typebox": "^1.1.5",
56
+ "ulidx": "^2.4.1",
47
57
  "ut-bitsyntax": "^6.2.7",
48
58
  "ut-bus": "^7.65.2",
49
59
  "ut-config": "^7.10.0",
@@ -52,7 +62,6 @@
52
62
  "ut-function.merge": "^1.5.6",
53
63
  "ut-function.timing": "^1.2.0",
54
64
  "ut-port": "^6.45.2",
55
- "ulidx": "^2.4.1",
56
65
  "uuid": "^13.0.0",
57
66
  "yaml": "^2.8.2"
58
67
  },
@@ -64,15 +73,6 @@
64
73
  "eslint": "~9.39.2",
65
74
  "typescript": "^5.9.3"
66
75
  },
67
- "exports": {
68
- ".": "./src/load.ts"
69
- },
70
- "repository": {
71
- "url": "git+https://github.com/feasibleone/blong.git"
72
- },
73
- "name": "@feasibleone/blong-gogo",
74
- "type": "module",
75
- "version": "1.7.2",
76
76
  "scripts": {
77
77
  "build": "true",
78
78
  "ci-publish": "node ../../common/scripts/install-run-rush-pnpm.js publish --access public --provenance"
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: TSchema,
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 || bodySchema?.additionalProperties)
115
+ (bodySchema.properties ||
116
+ ('additionalProperties' in bodySchema && bodySchema?.additionalProperties))
113
117
  ) {
114
- if (bodySchema.additionalProperties) Object.assign(result, request.body);
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<void> {
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<void> {
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> {
@@ -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<void>;
9
- stop: () => Promise<void>;
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<void> {
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<void> {
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<void> {
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<void>;
3
- stop: () => Promise<void>;
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
@@ -212,8 +212,4 @@ export default class RpcClientImpl extends RemoteImpl implements IRpcClient {
212
212
  return stream ? [sendRequest()] : sendRequest();
213
213
  };
214
214
  }
215
-
216
- public async stop(): Promise<void> {}
217
-
218
- public async start(): Promise<void> {}
219
215
  }
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<void> {
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<void> {
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,
@@ -172,10 +174,39 @@ export default class Watch extends Internal implements IWatch {
172
174
  const libs = [];
173
175
  const handlerFilenames = [];
174
176
  let latest = 0;
175
- const handlerFiles = (await scan(dir))
177
+ const allFiles = await scan(dir);
178
+ const configFile = allFiles.find(entry => entry.isFile() && isConfig(entry.name));
179
+ if (configFile) {
180
+ const configFilePath = join(dir, configFile.name);
181
+ const loaded = (
182
+ await import(
183
+ this.#config.enabled ? configFilePath + '?' + Date.now() : configFilePath
184
+ )
185
+ ).default;
186
+ const folderName = basename(dir);
187
+ const mutableConfig = config as Record<string, unknown>;
188
+ const configNames = (mutableConfig.configNames as string[]) ?? [];
189
+ const folderConfig =
190
+ loaded &&
191
+ typeof loaded === 'object' &&
192
+ typeof loaded.default === 'object' &&
193
+ loaded.default !== null
194
+ ? merge(
195
+ {},
196
+ ...['default', ...configNames].map(name => loaded[name]).filter(Boolean),
197
+ )
198
+ : (loaded ?? {});
199
+ const namespaceOverride = mutableConfig?.namespace?.[folderName] ?? {};
200
+ mutableConfig[folderName] = merge({}, folderConfig, namespaceOverride);
201
+ }
202
+ const handlerFiles = allFiles
176
203
  .sort()
177
204
  .filter(
178
- entry => entry.isFile() && isCode(entry.name) && !isLayerActivation(entry.name),
205
+ entry =>
206
+ entry.isFile() &&
207
+ isCode(entry.name) &&
208
+ !isLayerActivation(entry.name) &&
209
+ !isConfig(entry.name),
179
210
  );
180
211
  await this.#apiSchema.generateDir(dir, handlerFiles);
181
212
  for (const handlerEntry of handlerFiles) {
@@ -278,7 +309,7 @@ export default class Watch extends Internal implements IWatch {
278
309
  }
279
310
  }
280
311
 
281
- private _watch(registry: IRegistry): void {
312
+ private _watch(registry: IRegistry, configOverride: object): void {
282
313
  const fsWatcher = chokidar.watch(
283
314
  Array.from(this.#handlerFolders.keys())
284
315
  .map(folder => [
@@ -317,7 +348,7 @@ export default class Watch extends Internal implements IWatch {
317
348
  registry.ports.set(layerConfig.name + '.' + id, item.port);
318
349
  const port = await registry.createPort(layerConfig.name + '.' + id);
319
350
  if (!port) return;
320
- await port.start();
351
+ await port.start(configOverride);
321
352
  await port.ready();
322
353
  await registry.connected();
323
354
  this.#emit.emit('test');
@@ -361,7 +392,11 @@ export default class Watch extends Internal implements IWatch {
361
392
  });
362
393
  }
363
394
 
364
- public async start(registry: IRegistry, remote: IRemote): Promise<void> {
395
+ public async start(
396
+ registry: IRegistry,
397
+ remote: IRemote,
398
+ configOverride: object,
399
+ ): Promise<void> {
365
400
  this.log?.debug?.({
366
401
  $meta: {mtid: 'event', method: 'watch.start'},
367
402
  dir: Array.from(this.#handlerFolders.keys())
@@ -386,7 +421,7 @@ export default class Watch extends Internal implements IWatch {
386
421
  done?.();
387
422
  });
388
423
  }
389
- if (this.#config.enabled) this._watch(registry);
424
+ if (this.#config.enabled) this._watch(registry, configOverride);
390
425
 
391
426
  await registry.connected();
392
427
  }
@@ -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
- id,
60
- ...portApi
61
- }: Parameters<IAdapterFactory>[0] & {id: string}) => {
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
- ...moduleConfig?.[name],
66
- id,
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
- id,
78
- ...portApi
79
- }: Parameters<IAdapterFactory>[0] & {id: string}) => {
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
- await port.init({
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[],