@feasibleone/blong-gogo 1.18.1 → 1.19.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.
Files changed (64) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/package.json +2 -1
  3. package/src/AdapterBase.ts +51 -37
  4. package/src/ApiSchema.ts +100 -69
  5. package/src/BrowserLog.ts +18 -10
  6. package/src/ConfigRuntime.ts +7 -8
  7. package/src/ErrorFactory.ts +2 -2
  8. package/src/Gateway.ts +144 -109
  9. package/src/GatewayClient.ts +21 -8
  10. package/src/GatewayCodec.ts +31 -29
  11. package/src/Local.ts +4 -4
  12. package/src/Log.ts +4 -4
  13. package/src/Port.ts +2 -2
  14. package/src/Realm.ts +7 -7
  15. package/src/RealmDiscovery.ts +1 -1
  16. package/src/Registry.ts +44 -34
  17. package/src/Remote.ts +54 -40
  18. package/src/ResolutionDiscovery.ts +8 -7
  19. package/src/ResolutionLocal.ts +4 -0
  20. package/src/RestFs.ts +1 -1
  21. package/src/RpcClient.ts +10 -9
  22. package/src/RpcServer.ts +5 -5
  23. package/src/Watch.ts +108 -52
  24. package/src/adapter/browser/http.ts +4 -3
  25. package/src/adapter/server/github.ts +10 -10
  26. package/src/adapter/server/http.ts +10 -9
  27. package/src/adapter/server/k8s.ts +35 -22
  28. package/src/adapter/server/kafka.ts +118 -29
  29. package/src/adapter/server/keycloak.ts +38 -20
  30. package/src/adapter/server/knex.ts +19 -18
  31. package/src/adapter/server/mongodb.ts +64 -30
  32. package/src/adapter/server/s3.ts +27 -30
  33. package/src/adapter/server/slack.ts +4 -4
  34. package/src/adapter/server/tcp.ts +68 -54
  35. package/src/adapter/server/vault.ts +19 -19
  36. package/src/adapter/server/webhook.ts +20 -18
  37. package/src/busGateway.ts +116 -16
  38. package/src/chain.ts +40 -26
  39. package/src/codec/adapter/jsonrpc/receive.ts +1 -1
  40. package/src/codec/adapter/jsonrpc/send.ts +3 -3
  41. package/src/codec/adapter/mle/ready.ts +38 -25
  42. package/src/codec/adapter/openapi/load.ts +118 -95
  43. package/src/codec/adapter/openapi/ready.ts +15 -5
  44. package/src/codec/adapter/openapi/request.ts +35 -17
  45. package/src/error.proxy.test.ts +40 -49
  46. package/src/error.ts +43 -25
  47. package/src/folderAnalysis.ts +16 -13
  48. package/src/globals.d.ts +100 -0
  49. package/src/handlerProxy.ts +5 -6
  50. package/src/jose.ts +54 -28
  51. package/src/jwt.ts +6 -10
  52. package/src/layerProxy.ts +41 -29
  53. package/src/lib.ts +9 -7
  54. package/src/load.ts +95 -33
  55. package/src/loadApi.ts +1 -1
  56. package/src/loadBrowser.ts +10 -1
  57. package/src/loop.ts +168 -124
  58. package/src/mle.ts +40 -18
  59. package/src/oidc.ts +163 -53
  60. package/src/orchestrator/common/dispatch.ts +2 -2
  61. package/src/orchestrator/common/openapi.ts +9 -3
  62. package/src/runServer.ts +7 -7
  63. package/src/timeout.ts +12 -7
  64. package/src/tls.ts +4 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.19.0](https://github.com/feasibleone/blong/compare/blong-gogo-v1.18.1...blong-gogo-v1.19.0) (2026-04-29)
4
+
5
+
6
+ ### Features
7
+
8
+ * rename blong-int-sql → blong-int-adapter with full adapter integration test suite ([#135](https://github.com/feasibleone/blong/issues/135)) ([ee2b0f6](https://github.com/feasibleone/blong/commit/ee2b0f64b181f787a71c404e08fd62959a7f0e8e))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * type errors ([2f105bd](https://github.com/feasibleone/blong/commit/2f105bd3546b30ad1505ccde63ba43f3ccd575a8))
14
+ * type errors ([#133](https://github.com/feasibleone/blong/issues/133)) ([5b33e54](https://github.com/feasibleone/blong/commit/5b33e54b6b57eb561748a72f400a48f70af7f311))
15
+
3
16
  ## [1.18.1](https://github.com/feasibleone/blong/compare/blong-gogo-v1.18.0...blong-gogo-v1.18.1) (2026-04-26)
4
17
 
5
18
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@feasibleone/blong-gogo",
3
- "version": "1.18.1",
3
+ "version": "1.19.0",
4
4
  "repository": {
5
5
  "url": "git+https://github.com/feasibleone/blong.git"
6
6
  },
@@ -82,6 +82,7 @@
82
82
  "scripts": {
83
83
  "browser-check": "node scripts/browser-compat-check.mjs",
84
84
  "build": "true",
85
+ "ci-lint": "tsc --noEmit",
85
86
  "ci-publish": "node ../../common/scripts/install-run-rush-pnpm.js publish --access public --provenance",
86
87
  "ci-unit": "tap src/ConfigRuntime.test.ts src/lib.test.ts --allow-incomplete-coverage"
87
88
  }
@@ -1,11 +1,13 @@
1
1
  import type {
2
+ Adapter,
2
3
  Config,
3
4
  Errors,
4
- IAdapterFactory,
5
5
  IApi,
6
+ IContext,
6
7
  IErrorMap,
7
8
  IMeta,
8
9
  ITypedError,
10
+ PortHandlerBound,
9
11
  } from '@feasibleone/blong/types';
10
12
  import type net from 'node:net';
11
13
  import PQueue from 'p-queue';
@@ -70,14 +72,20 @@ const reserved: string[] = [
70
72
  * (`Object.setPrototypeOf(current, baseInstance)`), preserving the existing
71
73
  * hot-reload semantics.
72
74
  */
73
- export class AdapterBase<T = Record<string, unknown>, C = Record<string, unknown>> {
75
+ type AdapterHandlerContext = {
76
+ importedMap?: Map<string, object>;
77
+ imported: object;
78
+ config: {namespace?: string | string[]};
79
+ };
80
+
81
+ export class AdapterBase<T, C extends IContext> implements AdapterHandlerContext {
74
82
  errors = _errors;
75
83
  exec: unknown = null;
76
- imported: object = {};
84
+ imported: Record<string, PortHandlerBound> = {};
77
85
  config: Config<T, C> = {} as Config<T, C>;
78
86
  configBase: string;
79
87
  log: unknown = null;
80
- importedMap?: Map<string, object>;
88
+ importedMap?: Map<string, Record<string, (...args: unknown[]) => unknown>>;
81
89
 
82
90
  // These are prefixed with _ rather than using # private class fields.
83
91
  // The adapter uses Object.setPrototypeOf(current, base) to set the base as
@@ -98,9 +106,9 @@ export class AdapterBase<T = Record<string, unknown>, C = Record<string, unknown
98
106
  _createLog: IApi['createLog'];
99
107
  _attachCheckpoint: IApi['attachCheckpoint'];
100
108
  _activationNames: string[];
101
- _queue: PQueue;
109
+ _queue?: PQueue;
102
110
  _portLoop: any; // eslint-disable-line @typescript-eslint/no-explicit-any
103
- _resolveConnected: (value: boolean) => void;
111
+ _resolveConnected?: (value: boolean) => void;
104
112
  _connected: Promise<boolean>;
105
113
 
106
114
  constructor(
@@ -128,15 +136,16 @@ export class AdapterBase<T = Record<string, unknown>, C = Record<string, unknown
128
136
  this._createLog = api.createLog;
129
137
  this._attachCheckpoint = api.attachCheckpoint;
130
138
  this._activationNames = activationNames;
131
- let resolveConnected: (value: boolean) => void;
132
139
  this._connected = new Promise<boolean>(resolve => {
133
- resolveConnected = resolve;
140
+ this._resolveConnected = resolve;
134
141
  });
135
- this._resolveConnected = resolveConnected;
136
142
  }
137
143
 
138
144
  activeConfig(): object {
139
- return ConfigRuntime.mergeActivationConfig(this, this._activationNames);
145
+ return ConfigRuntime.mergeActivationConfig(
146
+ this as {activation?: Record<string, unknown>},
147
+ this._activationNames,
148
+ );
140
149
  }
141
150
 
142
151
  async init(...configs: object[]): Promise<void> {
@@ -175,16 +184,18 @@ export class AdapterBase<T = Record<string, unknown>, C = Record<string, unknown
175
184
  }
176
185
  }
177
186
 
178
- findValidation($meta: IMeta): unknown {
187
+ findValidation(): unknown {
179
188
  return null;
180
189
  }
181
190
 
182
191
  handles(name: string): boolean {
183
192
  if (reserved.includes(name)) return true;
184
193
  const id = this.config.id.replace(/\./g, '-');
185
- return []
194
+ return ([] as (string | RegExp)[])
186
195
  .concat(this.config.namespace || this.config.imports || id)
187
- .some(namespace => name.startsWith(namespace));
196
+ .some(namespace =>
197
+ typeof namespace === 'string' ? name.startsWith(namespace) : namespace.test(name),
198
+ );
188
199
  }
189
200
 
190
201
  methodPath(methodName: string): string {
@@ -195,9 +206,9 @@ export class AdapterBase<T = Record<string, unknown>, C = Record<string, unknown
195
206
  return methodName;
196
207
  }
197
208
 
198
- getConversion($meta: IMeta, type: 'send' | 'receive'): {fn: unknown; name: string} {
209
+ getConversion($meta: IMeta | false, type: 'send' | 'receive'): {fn: unknown; name: string} {
199
210
  let fn;
200
- let name: string;
211
+ let name: string = '';
201
212
  if ($meta) {
202
213
  if ($meta.method) {
203
214
  const path = this._getPath($meta.method);
@@ -238,18 +249,15 @@ export class AdapterBase<T = Record<string, unknown>, C = Record<string, unknown
238
249
  $meta: {mtid: 'event', method: `adapter.${event}`},
239
250
  ...data,
240
251
  });
241
- const eventHandlers = [];
252
+ const eventHandlers: Array<(...args: unknown[]) => unknown> = [];
242
253
  this.importedMap?.forEach(
243
254
  imp =>
244
- Object.prototype.hasOwnProperty.call(imp, event) &&
245
- eventHandlers.push(imp[event]),
255
+ Object.prototype.hasOwnProperty.call(imp, event) && eventHandlers.push(imp[event]),
246
256
  );
247
257
  let result: unknown = data;
248
258
  switch (mapper) {
249
259
  case 'asyncMap':
250
- result = await Promise.all(
251
- eventHandlers.map(handler => handler.call(this, data)),
252
- );
260
+ result = await Promise.all(eventHandlers.map(handler => handler.call(this, data)));
253
261
  break;
254
262
  case 'reduce':
255
263
  default:
@@ -269,11 +277,11 @@ export class AdapterBase<T = Record<string, unknown>, C = Record<string, unknown
269
277
  }
270
278
 
271
279
  async request(...params: unknown[]): Promise<unknown> {
272
- return this._queue.add(this._portLoop(params, true));
280
+ return this._queue!.add(this._portLoop(params, true));
273
281
  }
274
282
 
275
283
  async publish(...params: unknown[]): Promise<unknown> {
276
- await this._queue.add(this._portLoop(params, false));
284
+ await this._queue!.add(this._portLoop(params, false));
277
285
  return [true, params[params.length - 1]];
278
286
  }
279
287
 
@@ -283,7 +291,7 @@ export class AdapterBase<T = Record<string, unknown>, C = Record<string, unknown
283
291
 
284
292
  forNamespaces<R>(reducer: (prev: R, current: unknown) => R, initial: R): R {
285
293
  const id = this.config.id.replace(/\./g, '-');
286
- return []
294
+ return ([] as (string | RegExp)[])
287
295
  .concat(this.config.namespace || this.config.imports || id)
288
296
  .reduce(reducer.bind(this), initial);
289
297
  }
@@ -291,7 +299,13 @@ export class AdapterBase<T = Record<string, unknown>, C = Record<string, unknown
291
299
  async start(): Promise<unknown> {
292
300
  await this._api.attachHandlers(this, this.config.imports, true);
293
301
  const {req, pub} = this.forNamespaces(
294
- (prev, next) => {
302
+ (
303
+ prev: {
304
+ req: Record<string, (...args: unknown[]) => unknown>;
305
+ pub: Record<string, (...args: unknown[]) => unknown>;
306
+ },
307
+ next,
308
+ ) => {
295
309
  if (typeof next === 'string') {
296
310
  prev.req[`${next}.request`] = this.request.bind(this);
297
311
  prev.pub[`${next}.publish`] = this.publish.bind(this);
@@ -306,8 +320,11 @@ export class AdapterBase<T = Record<string, unknown>, C = Record<string, unknown
306
320
  return this.event('start', {configBase: this.configBase, config});
307
321
  }
308
322
 
309
- async link(patterns: unknown, target: {imported?: object} = {}): Promise<object> {
310
- await this._api.attachHandlers(target, patterns, false);
323
+ async link(
324
+ patterns: (string | RegExp)[] | string | RegExp,
325
+ target: AdapterHandlerContext = {} as unknown as AdapterHandlerContext,
326
+ ): Promise<object> {
327
+ await this._api.attachHandlers(target, patterns);
311
328
  return target.imported;
312
329
  }
313
330
 
@@ -323,14 +340,11 @@ export class AdapterBase<T = Record<string, unknown>, C = Record<string, unknown
323
340
  }
324
341
  }
325
342
 
326
- connect(
327
- what?: net.Socket | (() => void),
328
- context?: any, // eslint-disable-line @typescript-eslint/no-explicit-any
329
- ): void {
343
+ connect(what?: net.Socket | (() => void), context?: C): void {
330
344
  what ??= this.handle.bind(this);
331
345
  context ??= this.config.context;
332
346
  this._portLoop = loop(what, this as any, context); // eslint-disable-line @typescript-eslint/no-explicit-any
333
- this._resolveConnected(true);
347
+ this._resolveConnected?.(true);
334
348
  }
335
349
 
336
350
  async connected(): Promise<boolean> {
@@ -349,26 +363,26 @@ export class AdapterBase<T = Record<string, unknown>, C = Record<string, unknown
349
363
  * placed at the end of the handler prototype chain. This preserves the
350
364
  * constraint that realms/solutions never depend on blong-gogo.
351
365
  */
352
- export default async function adapter<T, C>(
366
+ export default async function adapter<T, C extends IContext>(
353
367
  api: IApi,
354
368
  configBase: string,
355
369
  activationNames: string[] = [],
356
- ): Promise<ReturnType<IAdapterFactory>> {
370
+ ): Promise<Adapter<T, C>> {
357
371
  const {adapter: adapterFactory, utError, handlers, remote, rpc, local, registry, type} = api;
358
372
  _errors ||= utError.register(errorMap);
359
373
 
360
374
  const base = new AdapterBase<T, C>(api, configBase, activationNames);
361
375
 
362
- const result = handlers({utError, remote, type});
376
+ const result = handlers!({utError, remote, type});
363
377
  let current = result;
364
378
  while (current.extends) {
365
379
  const parent = await (typeof current.extends === 'string'
366
- ? adapterFactory(current.extends)({utError, remote, rpc, local, registry})
380
+ ? adapterFactory(current.extends)!({utError, remote, rpc, local, registry})
367
381
  : current.extends({utError, remote, rpc, local, registry}));
368
382
  Object.setPrototypeOf(current, parent);
369
383
  current = parent;
370
384
  }
371
385
  Object.setPrototypeOf(current, base);
372
386
 
373
- return result as ReturnType<IAdapterFactory>;
387
+ return result as Adapter<T, C>;
374
388
  }
package/src/ApiSchema.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type {
2
+ ApiSchema as ApiSchemaType,
2
3
  GatewaySchema,
3
4
  IApiSchema,
4
5
  ILog,
@@ -41,7 +42,10 @@ export default class ApiSchema extends Internal implements IApiSchema {
41
42
  this.merge(this.#config, config);
42
43
  }
43
44
 
44
- public method(operation: {operationId?: string}): string {
45
+ public method(operation: {
46
+ operationId?: string;
47
+ 'x-blong-method'?: string;
48
+ }): string | undefined {
45
49
  return operation?.['x-blong-method'] || operation.operationId;
46
50
  }
47
51
 
@@ -75,8 +79,10 @@ export default class ApiSchema extends Internal implements IApiSchema {
75
79
  file.name.endsWith('.json'))
76
80
  ) {
77
81
  const [name] = this.#platform.basename(file.name).split('.');
78
- namespace[name] ||= [];
79
- namespace[name].push(this.#platform.join(dir, file.name));
82
+ (namespace as Record<string, string[]>)[name] ||= [];
83
+ (namespace as Record<string, string[]>)[name].push(
84
+ this.#platform.join(dir, file.name),
85
+ );
80
86
  }
81
87
  }
82
88
  }
@@ -89,13 +95,18 @@ export default class ApiSchema extends Internal implements IApiSchema {
89
95
  {},
90
96
  );
91
97
 
98
+ if (!namespace) return result;
99
+
92
100
  for (const [name, locations] of Object.entries(namespace)) {
93
101
  const bundle = await loadApi(locations, source, this.#platform);
94
- const {namespace = name, destination} = bundle['x-blong'] ?? {};
95
- this.#namespace[namespace] ||= {};
96
- Object.entries(bundle.paths).forEach(([path, methods]: [string, PathItemObject]) => {
97
- ['get', 'post', 'put', 'delete'].forEach(
98
- (httpMethod: 'get' | 'post' | 'put' | 'delete') => {
102
+ const blongMeta = (bundle as Record<string, unknown>)['x-blong'] as
103
+ | {namespace?: string; destination?: string}
104
+ | undefined;
105
+ const {namespace: nsName = name, destination} = blongMeta ?? {};
106
+ this.#namespace[nsName] ||= {};
107
+ Object.entries(bundle.paths ?? {}).forEach(
108
+ ([path, methods]: [string, PathItemObject]) => {
109
+ (['get', 'post', 'put', 'delete'] as const).forEach(httpMethod => {
99
110
  const operation = methods[httpMethod];
100
111
  if (!operation) return;
101
112
  const bodyParam = (
@@ -105,39 +116,46 @@ export default class ApiSchema extends Internal implements IApiSchema {
105
116
  const definition: GatewaySchema = {
106
117
  rpc: false,
107
118
  auth: false,
108
- ...(bodyParam && {
109
- body: bodyParam,
110
- }),
119
+ ...(bodyParam ? {body: bodyParam} : {}),
111
120
  ...('requestBody' in operation && {
112
121
  body:
113
122
  'openapi' in bundle
114
- ? 'content' in operation.requestBody &&
115
- operation.requestBody.content?.['application/json']
123
+ ? 'content' in (operation.requestBody ?? {}) &&
124
+ (
125
+ operation.requestBody as {
126
+ content?: Record<string, {schema?: unknown}>;
127
+ }
128
+ )?.content?.['application/json']
116
129
  : operation.requestBody,
117
130
  }),
118
- basePath: `/rest/${namespace}`,
119
- response: (operation.responses?.['200'] as {content: unknown})
120
- ?.content?.['application/json']?.schema,
131
+ basePath: `/rest/${nsName}`,
132
+ response: (
133
+ (
134
+ operation.responses?.['200'] as {
135
+ content?: Record<string, {schema?: unknown}>;
136
+ }
137
+ )?.content?.['application/json'] as {schema?: unknown} | undefined
138
+ )?.schema as ApiSchemaType | undefined,
121
139
  description: operation.description,
122
140
  summary: operation.summary,
123
141
  destination,
124
142
  method: httpMethod.toUpperCase() as Uppercase<typeof httpMethod>,
125
- subject: namespace,
143
+ subject: nsName,
126
144
  operation,
127
145
  path: path.replaceAll('{', ':').replaceAll('}', ''),
128
146
  };
129
- this.#loaded[`${namespace}${method}`.toLowerCase()] = definition;
130
- this.#namespace[namespace][`${namespace}.${method}`.toLowerCase()] =
131
- definition;
132
- result[`${namespace}.${method}`.toLowerCase()] = definition;
133
- },
134
- );
135
- });
147
+ this.#loaded[`${nsName}${method}`.toLowerCase()] = definition;
148
+ this.#namespace[nsName][`${nsName}.${method}`.toLowerCase()] = definition;
149
+ result[`${nsName}.${method}`.toLowerCase()] = definition;
150
+ });
151
+ },
152
+ );
136
153
  }
137
154
  const generate = [];
138
155
  for (const [prefix, record] of Object.entries(this.#generateDir)) {
139
156
  for (const [method, operation] of Object.entries(this.#loaded)) {
140
- const filename = operation.subject + this.method(operation.operation) + '.ts';
157
+ const filename =
158
+ (operation.subject ?? '') + this.method(operation.operation ?? {}) + '.ts';
141
159
  if (method.startsWith(prefix) && !record.existing.has(filename.toLowerCase())) {
142
160
  generate.push(this.#platform.join(record.dir, filename));
143
161
  }
@@ -179,53 +197,61 @@ export default handler(
179
197
  }
180
198
 
181
199
  private _params(schema: GatewaySchema): string {
182
- return schema?.operation?.parameters
183
- ?.map((param: (typeof schema.operation.parameters)[0]) => {
184
- if ('$ref' in param) return '';
185
- if (!('in' in param)) return;
186
- switch (param.in) {
187
- case 'header':
188
- case 'path':
189
- case 'query':
190
- return ` ${identifier(param.name)}${
191
- param.required ? ':' : '?:'
192
- } ${this._paramType(param)};${
193
- param.description
194
- ? ` // ${param.description.replaceAll(/[\r\n]/g, '')}`
195
- : ''
196
- }`;
197
- case 'body':
198
- if (param.schema?.type === 'object') {
199
- return Object.entries(param.schema.properties)
200
- .map(
201
- ([name, property]: [string, {description?: string}]) =>
202
- ` ${name}${param.required ? ':' : '?:'} ${this._type(
203
- property,
204
- )};${
205
- property.description
206
- ? ` // ${property.description.replaceAll(
207
- /[\r\n]/g,
208
- '',
209
- )}`
210
- : ''
211
- }`,
200
+ return (
201
+ schema?.operation?.parameters
202
+ ?.map((param: (typeof schema.operation.parameters)[0]) => {
203
+ if ('$ref' in param) return '';
204
+ if (!('in' in param)) return;
205
+ switch (param.in) {
206
+ case 'header':
207
+ case 'path':
208
+ case 'query':
209
+ return ` ${identifier(param.name)}${
210
+ param.required ? ':' : '?:'
211
+ } ${this._paramType(param)};${
212
+ param.description
213
+ ? ` // ${param.description.replaceAll(/[\r\n]/g, '')}`
214
+ : ''
215
+ }`;
216
+ case 'body':
217
+ if (param.schema?.type === 'object') {
218
+ return Object.entries(
219
+ (param.schema.properties as Record<
220
+ string,
221
+ {description?: string}
222
+ >) ?? {},
212
223
  )
213
- .join('\n');
214
- }
215
- }
216
- })
217
- .filter(Boolean)
218
- .join('\n');
224
+ .map(
225
+ ([name, property]: [string, {description?: string}]) =>
226
+ ` ${name}${param.required ? ':' : '?:'} ${this._type(
227
+ property,
228
+ )};${
229
+ property.description
230
+ ? ` // ${property.description.replaceAll(
231
+ /[\r\n]/g,
232
+ '',
233
+ )}`
234
+ : ''
235
+ }`,
236
+ )
237
+ .join('\n');
238
+ }
239
+ }
240
+ })
241
+ .filter(Boolean)
242
+ .join('\n') ?? ''
243
+ );
219
244
  }
220
245
 
221
246
  private _response({operation}: GatewaySchema): string {
222
247
  if (!operation?.responses || !(200 in operation.responses)) return '';
223
- if (!('schema' in operation.responses[200])) return '';
224
- const schema = operation.responses?.[200]?.schema;
225
- if (!schema || !('properties' in schema)) return '';
226
- return Object.entries(schema.properties ?? {})
248
+ const resp200 = operation.responses[200] as Record<string, unknown> | undefined;
249
+ if (!resp200 || !('schema' in resp200)) return '';
250
+ const schema = resp200?.['schema'];
251
+ if (!schema || typeof schema !== 'object' || !('properties' in schema)) return '';
252
+ return Object.entries((schema as {properties: Record<string, unknown>}).properties ?? {})
227
253
  .map(([name, property]) => {
228
- return ` ${name}: ${this._type(property)},`;
254
+ return ` ${name}: ${this._type(property as SchemaObject)},`;
229
255
  })
230
256
  .join('\n');
231
257
  }
@@ -253,11 +279,15 @@ export default handler(
253
279
  case 'boolean':
254
280
  return 'boolean';
255
281
  case 'array':
256
- return `${this._type(schema.items)}[]`;
282
+ return `${this._type(schema.items as SchemaObject)}[]`;
257
283
  case 'object':
258
- return `{${Object.entries(schema.properties)
259
- .map(([name, property]) => `${name}: ${this._type(property)}`)
284
+ return `{${Object.entries(
285
+ (schema as {properties?: Record<string, unknown>}).properties ?? {},
286
+ )
287
+ .map(([name, property]) => `${name}: ${this._type(property as SchemaObject)}`)
260
288
  .join('; ')}}`;
289
+ default:
290
+ return 'unknown';
261
291
  }
262
292
  }
263
293
 
@@ -290,5 +320,6 @@ export default handler(
290
320
  files.forEach(file =>
291
321
  record.existing.add(this.#platform.basename(file.name).toLowerCase()),
292
322
  );
323
+ return true;
293
324
  }
294
325
  }
package/src/BrowserLog.ts CHANGED
@@ -61,16 +61,29 @@ interface ISimpleLogger {
61
61
  fatal(obj: unknown, msg?: string): void;
62
62
  }
63
63
 
64
- function write(rec: Record<string, unknown>, logByLevel: boolean): void {
64
+ function write(
65
+ rec: {
66
+ time?: number;
67
+ level?: number | string;
68
+ name?: string;
69
+ childName?: string;
70
+ context?: string;
71
+ prefix?: string;
72
+ $meta?: {mtid?: string; method?: string};
73
+ msg?: string;
74
+ error?: unknown;
75
+ },
76
+ logByLevel: boolean,
77
+ ): void {
65
78
  const levelNum = rec.level as number;
66
79
  const levelKey = NAME_FROM_VALUE[levelNum] ?? 'info';
67
80
  const paddedLevel = levelKey.toUpperCase().padStart(5);
68
81
 
69
- let consoleMethod: (...a: unknown[]) => void = console.log; // eslint-disable-line no-console
82
+ let consoleMethod: (...a: unknown[]) => void = console.log;
70
83
  if (logByLevel) {
71
84
  const mapped = levelNum <= 10 ? 'debug' : levelNum >= 60 ? 'error' : levelKey;
72
- const c = console as unknown as Record<string, (...a: unknown[]) => void>; // eslint-disable-line no-console
73
- consoleMethod = typeof c[mapped] === 'function' ? c[mapped] : console.log; // eslint-disable-line no-console
85
+ const c = console as unknown as Record<string, (...a: unknown[]) => void>;
86
+ consoleMethod = typeof c[mapped] === 'function' ? c[mapped] : console.log;
74
87
  }
75
88
 
76
89
  const levelCss = LEVEL_CSS[levelKey] ?? LEVEL_CSS.info;
@@ -110,7 +123,7 @@ function write(rec: Record<string, unknown>, logByLevel: boolean): void {
110
123
 
111
124
  consoleMethod(...args);
112
125
 
113
- if (rec.error && (rec.error as {stack?: string}).stack) console.error(rec.error); // eslint-disable-line no-console
126
+ if (rec.error && (rec.error as {stack?: string}).stack) console.error(rec.error);
114
127
  }
115
128
 
116
129
  function createLogger(
@@ -183,19 +196,14 @@ export default class BrowserLog extends Internal implements ILog {
183
196
  switch (level) {
184
197
  case 'trace':
185
198
  result.trace = child.trace.bind(child) as ILogger['trace'];
186
- // eslint-disable-next-line no-fallthrough
187
199
  case 'debug':
188
200
  result.debug = child.debug.bind(child) as ILogger['debug'];
189
- // eslint-disable-next-line no-fallthrough
190
201
  case 'info':
191
202
  result.info = child.info.bind(child) as ILogger['info'];
192
- // eslint-disable-next-line no-fallthrough
193
203
  case 'warn':
194
204
  result.warn = child.warn.bind(child) as ILogger['warn'];
195
- // eslint-disable-next-line no-fallthrough
196
205
  case 'error':
197
206
  result.error = child.error.bind(child) as ILogger['error'];
198
- // eslint-disable-next-line no-fallthrough
199
207
  case 'fatal':
200
208
  result.fatal = child.fatal.bind(child) as ILogger['fatal'];
201
209
  }
@@ -37,7 +37,7 @@ import merge from 'ut-function.merge';
37
37
  * are path-based views over the same root backing cell.
38
38
  *
39
39
  * IMPORTANT: the proxy is transparent — `typeof proxy`, `Array.isArray`, JSON
40
- * serialisation, and property enumeration all behave as they would on the
40
+ * serialization, and property enumeration all behave as they would on the
41
41
  * underlying plain object. This means existing code that treats `config` as a
42
42
  * plain object continues to work without modification.
43
43
  */
@@ -130,7 +130,7 @@ export function createConfigProxy<T extends object>(
130
130
  }
131
131
 
132
132
  const pathProxy = new Proxy({} as T, {
133
- get(_target, prop, _receiver) {
133
+ get(_target, prop) {
134
134
  const container = getNode(path);
135
135
  const val = container == null ? undefined : Reflect.get(container as object, prop);
136
136
  if (val === undefined || val === null || typeof val !== 'object') {
@@ -142,7 +142,7 @@ export function createConfigProxy<T extends object>(
142
142
  ) {
143
143
  const error = new Error(
144
144
  `Config hot-reload anti-pattern: '${prop}' is a primitive read from the config ` +
145
- `proxy during handler factory initialisation. The value will be captured once ` +
145
+ `proxy during handler factory initialization. The value will be captured once ` +
146
146
  `and will NOT reflect future config reloads.\n` +
147
147
  `Fix: read the value inside the handler body instead:\n` +
148
148
  ` ✅ handler(({config}) => ({ fn: () => config.${prop} }))\n` +
@@ -165,7 +165,7 @@ export function createConfigProxy<T extends object>(
165
165
  if (container == null) return false;
166
166
  return Reflect.has(container as object, prop);
167
167
  },
168
- ownKeys(_target) {
168
+ ownKeys() {
169
169
  const container = getNode(path);
170
170
  if (container == null) return [];
171
171
  return Reflect.ownKeys(container as object);
@@ -175,7 +175,7 @@ export function createConfigProxy<T extends object>(
175
175
  if (container == null) return undefined;
176
176
  return Object.getOwnPropertyDescriptor(container as object, prop);
177
177
  },
178
- getPrototypeOf(_target) {
178
+ getPrototypeOf() {
179
179
  const container = getNode(path);
180
180
  if (container == null) return Object.getPrototypeOf({});
181
181
  return Object.getPrototypeOf(container as object);
@@ -316,7 +316,6 @@ export default class ConfigRuntime implements IConfigRuntime {
316
316
  await subscriber(diff, next, prev);
317
317
  } catch (error) {
318
318
  // Subscriber errors must not break the reload pipeline
319
- // eslint-disable-next-line no-console
320
319
  console.error('[ConfigRuntime] subscriber error:', error);
321
320
  }
322
321
  }
@@ -338,7 +337,7 @@ export default class ConfigRuntime implements IConfigRuntime {
338
337
  }
339
338
 
340
339
  // -----------------------------------------------------------------------
341
- // Centralised config merge methods
340
+ // Centralized config merge methods
342
341
  //
343
342
  // These replace the scattered merge operations in loadRealm(), layerProxy,
344
343
  // and adapter.activeConfig(). Each method encapsulates a specific merge
@@ -372,7 +371,7 @@ export default class ConfigRuntime implements IConfigRuntime {
372
371
  * @param activationNames - active environment names (e.g. `['dev']`)
373
372
  */
374
373
  static mergeActivationConfig(
375
- target: object,
374
+ target: {activation?: Record<string, unknown>},
376
375
  activationNames: string[] = [],
377
376
  ): object {
378
377
  return merge([
@@ -19,8 +19,8 @@ export default class ErrorFactory extends Internal implements IErrorFactory {
19
19
  this.merge(this.#config, config);
20
20
  this.#error = Errors({
21
21
  logFactory: {
22
- createLog: (logLevel, bindings) => log?.logger(logLevel, bindings) || {},
23
- },
22
+ createLog: (...params: Parameters<ILog['logger']>) => log?.logger(...params) || {},
23
+ } as unknown as Parameters<typeof Errors>[0]['logFactory'],
24
24
  logLevel: this.#config.logLevel,
25
25
  errorPrint: this.#config.errorPrint,
26
26
  });