@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 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
@@ -9,5 +9,5 @@ else
9
9
  fi
10
10
  blong_gogo=$(npm ls -g @feasibleone/blong-gogo --parseable)
11
11
  mv -f "$blong_gogo" ./
12
- cp -r blong-gogo/bin/blong.sh "$blong_gogo"/bin/
12
+ cp -r blong-gogo/bin/blong-gogo.sh "$blong_gogo"/bin/
13
13
  ./blong-gogo/bin/blong.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feasibleone/blong-gogo",
3
- "version": "1.7.3",
3
+ "version": "1.8.1",
4
4
  "repository": {
5
5
  "url": "git+https://github.com/feasibleone/blong.git"
6
6
  },
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,
@@ -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
- ${TypeScriptToTypeBox.Generate(schema.sort().join('\n')).trim()}
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 handlerFiles = (await scan(dir))
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 => entry.isFile() && isCode(entry.name) && !isLayerActivation(entry.name),
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(registry: IRegistry, remote: IRemote): Promise<void> {
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
- 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[],