@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.
- package/CHANGELOG.md +13 -0
- package/package.json +2 -1
- package/src/AdapterBase.ts +51 -37
- package/src/ApiSchema.ts +100 -69
- package/src/BrowserLog.ts +18 -10
- package/src/ConfigRuntime.ts +7 -8
- package/src/ErrorFactory.ts +2 -2
- package/src/Gateway.ts +144 -109
- package/src/GatewayClient.ts +21 -8
- package/src/GatewayCodec.ts +31 -29
- package/src/Local.ts +4 -4
- package/src/Log.ts +4 -4
- package/src/Port.ts +2 -2
- package/src/Realm.ts +7 -7
- package/src/RealmDiscovery.ts +1 -1
- package/src/Registry.ts +44 -34
- package/src/Remote.ts +54 -40
- package/src/ResolutionDiscovery.ts +8 -7
- package/src/ResolutionLocal.ts +4 -0
- package/src/RestFs.ts +1 -1
- package/src/RpcClient.ts +10 -9
- package/src/RpcServer.ts +5 -5
- package/src/Watch.ts +108 -52
- package/src/adapter/browser/http.ts +4 -3
- package/src/adapter/server/github.ts +10 -10
- package/src/adapter/server/http.ts +10 -9
- package/src/adapter/server/k8s.ts +35 -22
- package/src/adapter/server/kafka.ts +118 -29
- package/src/adapter/server/keycloak.ts +38 -20
- package/src/adapter/server/knex.ts +19 -18
- package/src/adapter/server/mongodb.ts +64 -30
- package/src/adapter/server/s3.ts +27 -30
- package/src/adapter/server/slack.ts +4 -4
- package/src/adapter/server/tcp.ts +68 -54
- package/src/adapter/server/vault.ts +19 -19
- package/src/adapter/server/webhook.ts +20 -18
- package/src/busGateway.ts +116 -16
- package/src/chain.ts +40 -26
- package/src/codec/adapter/jsonrpc/receive.ts +1 -1
- package/src/codec/adapter/jsonrpc/send.ts +3 -3
- package/src/codec/adapter/mle/ready.ts +38 -25
- package/src/codec/adapter/openapi/load.ts +118 -95
- package/src/codec/adapter/openapi/ready.ts +15 -5
- package/src/codec/adapter/openapi/request.ts +35 -17
- package/src/error.proxy.test.ts +40 -49
- package/src/error.ts +43 -25
- package/src/folderAnalysis.ts +16 -13
- package/src/globals.d.ts +100 -0
- package/src/handlerProxy.ts +5 -6
- package/src/jose.ts +54 -28
- package/src/jwt.ts +6 -10
- package/src/layerProxy.ts +41 -29
- package/src/lib.ts +9 -7
- package/src/load.ts +95 -33
- package/src/loadApi.ts +1 -1
- package/src/loadBrowser.ts +10 -1
- package/src/loop.ts +168 -124
- package/src/mle.ts +40 -18
- package/src/oidc.ts +163 -53
- package/src/orchestrator/common/dispatch.ts +2 -2
- package/src/orchestrator/common/openapi.ts +9 -3
- package/src/runServer.ts +7 -7
- package/src/timeout.ts +12 -7
- 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.
|
|
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
|
}
|
package/src/AdapterBase.ts
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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,
|
|
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
|
|
109
|
+
_queue?: PQueue;
|
|
102
110
|
_portLoop: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
103
|
-
_resolveConnected
|
|
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
|
-
|
|
140
|
+
this._resolveConnected = resolve;
|
|
134
141
|
});
|
|
135
|
-
this._resolveConnected = resolveConnected;
|
|
136
142
|
}
|
|
137
143
|
|
|
138
144
|
activeConfig(): object {
|
|
139
|
-
return ConfigRuntime.mergeActivationConfig(
|
|
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(
|
|
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 =>
|
|
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
|
|
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
|
|
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
|
-
(
|
|
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(
|
|
310
|
-
|
|
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<
|
|
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
|
|
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: {
|
|
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(
|
|
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
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
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/${
|
|
119
|
-
response: (
|
|
120
|
-
|
|
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:
|
|
143
|
+
subject: nsName,
|
|
126
144
|
operation,
|
|
127
145
|
path: path.replaceAll('{', ':').replaceAll('}', ''),
|
|
128
146
|
};
|
|
129
|
-
this.#loaded[`${
|
|
130
|
-
this.#namespace[
|
|
131
|
-
|
|
132
|
-
|
|
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 =
|
|
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
|
|
183
|
-
?.
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
param.
|
|
192
|
-
|
|
193
|
-
param
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
.
|
|
201
|
-
(
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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(
|
|
259
|
-
|
|
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(
|
|
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;
|
|
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>;
|
|
73
|
-
consoleMethod = typeof c[mapped] === 'function' ? c[mapped] : console.log;
|
|
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);
|
|
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
|
}
|
package/src/ConfigRuntime.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
|
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
|
|
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(
|
|
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(
|
|
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
|
-
//
|
|
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:
|
|
374
|
+
target: {activation?: Record<string, unknown>},
|
|
376
375
|
activationNames: string[] = [],
|
|
377
376
|
): object {
|
|
378
377
|
return merge([
|
package/src/ErrorFactory.ts
CHANGED
|
@@ -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: (
|
|
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
|
});
|