@feasibleone/blong-gogo 1.17.0 → 1.18.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 +28 -0
- package/bin/blong-dev.ts +2 -2
- package/bin/blong.ts +1 -1
- package/package.json +13 -15
- package/src/AdapterBase.ts +374 -0
- package/src/ApiSchema.ts +25 -24
- package/src/BrowserLog.ts +139 -79
- package/src/ConfigRuntime.test.ts +19 -74
- package/src/ConfigRuntime.ts +130 -107
- package/src/Gateway.ts +17 -5
- package/src/GatewayClient.ts +1 -2
- package/src/Log.ts +79 -22
- package/src/Port.ts +8 -1
- package/src/Realm.ts +5 -5
- package/src/Registry.ts +44 -24
- package/src/Remote.ts +17 -6
- package/src/ResolutionDiscovery.ts +13 -5
- package/src/RpcClient.ts +15 -3
- package/src/Watch.test.ts +51 -0
- package/src/Watch.ts +205 -115
- package/src/adapter/browser/generic.ts +15 -0
- package/src/adapter/browser.ts +2 -1
- package/src/codec/adapter/jsonrpc/send.ts +1 -2
- package/src/codec/adapter/openapi/load.ts +2 -4
- package/src/codec/adapter/openapi/ready.ts +1 -1
- package/src/codec/browser.ts +3 -1
- package/src/handlerProxy.ts +165 -0
- package/src/kopi.ts +1 -1
- package/src/layerProxy.ts +151 -374
- package/src/load.ts +317 -132
- package/src/loadApi.ts +22 -12
- package/src/loadBrowser.ts +73 -0
- package/src/loadServer.ts +44 -69
- package/src/loop.ts +2 -2
- package/src/orchestrator/common/openapi.ts +1 -1
- package/src/orchestrator/index.ts +1 -1
- package/src/pino-cacache.ts +123 -0
- package/src/pino-pretty.ts +5 -1
- package/src/runServer.ts +73 -0
- package/src/timing.ts +29 -0
- package/src/adapter.ts +0 -304
- package/src/browser.ts +0 -4
- package/src/scan.ts +0 -12
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,33 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.18.0](https://github.com/feasibleone/blong/compare/blong-gogo-v1.17.1...blong-gogo-v1.18.0) (2026-04-26)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add support for the browser platform ([f483e14](https://github.com/feasibleone/blong/commit/f483e149711acdeebcf9ee5afa3bf7ac7a6b669c))
|
|
9
|
+
* implement wiring pipeline simplifications ([0a3dc01](https://github.com/feasibleone/blong/commit/0a3dc01e97c158d30491e8b76bef7d6bb4987529))
|
|
10
|
+
* model handling ([168e77e](https://github.com/feasibleone/blong/commit/168e77ec3284bbcebf5d7d6bb07cc624d0d1a56e))
|
|
11
|
+
* model mocks ([118a142](https://github.com/feasibleone/blong/commit/118a14254f87b8d1cf0b4d217939a9d49942f93e))
|
|
12
|
+
* pino cacache log transport with ULID-linked terminal entries and VSCode click-to-inspect ([#126](https://github.com/feasibleone/blong/issues/126)) ([b3a91f5](https://github.com/feasibleone/blong/commit/b3a91f5d8e6fab1eaf02b81bbd20f2136e5ef267))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* dependencies ([a21d8eb](https://github.com/feasibleone/blong/commit/a21d8ebe4ce9b476060dec8eeb9017a5eb7f07b5))
|
|
18
|
+
* dependencies ([705b2f7](https://github.com/feasibleone/blong/commit/705b2f7a72ac2dc93aea0f586394dde71192c1b6))
|
|
19
|
+
* eslint ([a3b9f2b](https://github.com/feasibleone/blong/commit/a3b9f2bed4f958abfb378d97d372b3e7a6cd5a21))
|
|
20
|
+
* generic adapter ([e5ac7e7](https://github.com/feasibleone/blong/commit/e5ac7e7073aaf0e12258830585a38b055e2905ae))
|
|
21
|
+
* optimize browser loading ([0aac985](https://github.com/feasibleone/blong/commit/0aac985a24520810dc05eb34d314ed7dec4f1962))
|
|
22
|
+
* remove heft lint ([c4d0eaa](https://github.com/feasibleone/blong/commit/c4d0eaa81714c04e9f9adec01c6ae7fa068f1948))
|
|
23
|
+
|
|
24
|
+
## [1.17.1](https://github.com/feasibleone/blong/compare/blong-gogo-v1.17.0...blong-gogo-v1.17.1) (2026-04-10)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
### Bug Fixes
|
|
28
|
+
|
|
29
|
+
* build ([32951e4](https://github.com/feasibleone/blong/commit/32951e46561648651ee4e032545655007c05e274))
|
|
30
|
+
|
|
3
31
|
## [1.17.0](https://github.com/feasibleone/blong/compare/blong-gogo-v1.16.0...blong-gogo-v1.17.0) (2026-04-10)
|
|
4
32
|
|
|
5
33
|
|
package/bin/blong-dev.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
#!/usr/bin/env -S node --watch --inspect
|
|
1
|
+
#!/usr/bin/env -S node --watch --conditions=development --inspect
|
|
2
2
|
|
|
3
3
|
import minimist from 'minimist';
|
|
4
|
-
import {autoRun} from '../src/
|
|
4
|
+
import {autoRun} from '../src/runServer.ts';
|
|
5
5
|
|
|
6
6
|
const argv: {_: string[]} = minimist(process.argv.slice(2));
|
|
7
7
|
|
package/bin/blong.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feasibleone/blong-gogo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.18.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"url": "git+https://github.com/feasibleone/blong.git"
|
|
6
6
|
},
|
|
7
7
|
"type": "module",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
|
-
"browser": "./src/
|
|
11
|
-
"import": "./src/
|
|
10
|
+
"browser": "./src/loadBrowser.ts",
|
|
11
|
+
"import": "./src/loadServer.ts"
|
|
12
12
|
},
|
|
13
|
-
"./browser": "./src/adapter/browser.ts",
|
|
14
13
|
"./ConfigRuntime.js": "./src/ConfigRuntime.ts"
|
|
15
14
|
},
|
|
16
15
|
"bin": {
|
|
@@ -20,7 +19,7 @@
|
|
|
20
19
|
},
|
|
21
20
|
"dependencies": {
|
|
22
21
|
"@apidevtools/swagger-parser": "^12.1.0",
|
|
23
|
-
"@aws-sdk/client-s3": "^3.
|
|
22
|
+
"@aws-sdk/client-s3": "^3.1037.0",
|
|
24
23
|
"@fastify/basic-auth": "^6.2.0",
|
|
25
24
|
"@fastify/bearer-auth": "^10.1.2",
|
|
26
25
|
"@fastify/cookie": "^11.0.2",
|
|
@@ -38,13 +37,12 @@
|
|
|
38
37
|
"@slack/webhook": "^7.0.7",
|
|
39
38
|
"ajv-formats": "^3.0.1",
|
|
40
39
|
"browser-process-hrtime": "^1.0.0",
|
|
40
|
+
"cacache": "^20.0.4",
|
|
41
41
|
"chokidar": "^5.0.0",
|
|
42
|
-
"fastify": "^5.8.
|
|
42
|
+
"fastify": "^5.8.4",
|
|
43
43
|
"fastify-plugin": "^5.1.0",
|
|
44
|
-
"glob": "^13.0.6",
|
|
45
44
|
"got": "^14.6.6",
|
|
46
45
|
"jose": "^6.2.1",
|
|
47
|
-
"undici": "^8.0.1",
|
|
48
46
|
"knex": "^3.1.0",
|
|
49
47
|
"ky": "^1.14.2",
|
|
50
48
|
"lru-cache": "^11.2.4",
|
|
@@ -57,33 +55,33 @@
|
|
|
57
55
|
"openapi-types": "^12.1.3",
|
|
58
56
|
"p-queue": "^9.1.0",
|
|
59
57
|
"pino": "^10.3.1",
|
|
58
|
+
"pino-abstract-transport": "^3.0.0",
|
|
60
59
|
"pino-pretty": "^13.1.3",
|
|
61
60
|
"reconnect-core": "^1.3.0",
|
|
61
|
+
"tinyglobby": "^0.2.16",
|
|
62
62
|
"typebox": "^1.1.5",
|
|
63
63
|
"ulidx": "^2.4.1",
|
|
64
|
+
"undici": "^8.0.1",
|
|
64
65
|
"ut-bitsyntax": "^6.2.7",
|
|
65
66
|
"ut-dns-discovery": "^6.2.3",
|
|
66
67
|
"ut-function.interpolate": "1.1.3",
|
|
67
68
|
"ut-function.merge": "^1.5.6",
|
|
68
|
-
"ut-function.timing": "^1.2.0",
|
|
69
|
-
"ut-port": "^6.45.2",
|
|
70
69
|
"uuid": "^13.0.0",
|
|
71
|
-
"yaml": "^2.8.
|
|
70
|
+
"yaml": "^2.8.3"
|
|
72
71
|
},
|
|
73
72
|
"devDependencies": {
|
|
74
|
-
"@rushstack/eslint-config": "^4.6.4",
|
|
75
73
|
"@rushstack/heft": "^1.2.6",
|
|
76
74
|
"@rushstack/heft-lint-plugin": "^1.2.6",
|
|
77
75
|
"@rushstack/heft-typescript-plugin": "^1.3.1",
|
|
76
|
+
"@types/cacache": "^20.0.1",
|
|
78
77
|
"@types/node": "^24",
|
|
79
|
-
"eslint": "~9.39.2",
|
|
80
78
|
"playwright": "^1.58.2",
|
|
81
|
-
"tap": "^21.6.
|
|
79
|
+
"tap": "^21.6.3",
|
|
82
80
|
"typescript": "^5.9.3"
|
|
83
81
|
},
|
|
84
82
|
"scripts": {
|
|
85
|
-
"build": "true",
|
|
86
83
|
"browser-check": "node scripts/browser-compat-check.mjs",
|
|
84
|
+
"build": "true",
|
|
87
85
|
"ci-publish": "node ../../common/scripts/install-run-rush-pnpm.js publish --access public --provenance",
|
|
88
86
|
"ci-unit": "tap src/ConfigRuntime.test.ts src/lib.test.ts --allow-incomplete-coverage"
|
|
89
87
|
}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Config,
|
|
3
|
+
Errors,
|
|
4
|
+
IAdapterFactory,
|
|
5
|
+
IApi,
|
|
6
|
+
IErrorMap,
|
|
7
|
+
IMeta,
|
|
8
|
+
ITypedError,
|
|
9
|
+
} from '@feasibleone/blong/types';
|
|
10
|
+
import type net from 'node:net';
|
|
11
|
+
import PQueue from 'p-queue';
|
|
12
|
+
import merge from 'ut-function.merge';
|
|
13
|
+
|
|
14
|
+
import ConfigRuntime from './ConfigRuntime.ts';
|
|
15
|
+
import loop from './loop.ts';
|
|
16
|
+
|
|
17
|
+
const errorMap: IErrorMap = {
|
|
18
|
+
'adapter.configValidation': 'Adapter config validation:\r\n{message}',
|
|
19
|
+
'adapter.missingParameters': 'Missing parameters',
|
|
20
|
+
'adapter.missingMeta': 'Missing metadata',
|
|
21
|
+
'adapter.notConnected': 'No connection',
|
|
22
|
+
'adapter.disconnect': 'Adapter disconnected',
|
|
23
|
+
'adapter.disconnectBeforeResponse': 'Disconnect before response received',
|
|
24
|
+
'adapter.stream': 'Adapter stream error',
|
|
25
|
+
'adapter.timeout': 'Timeout',
|
|
26
|
+
'adapter.echoTimeout': 'Echo retries limit exceeded',
|
|
27
|
+
'adapter.unhandled': 'Unhandled adapter error',
|
|
28
|
+
'adapter.bufferOverflow': 'Message size of {size} exceeds the maximum of {max}',
|
|
29
|
+
'adapter.socketTimeout': 'Socket timeout',
|
|
30
|
+
'adapter.receiveTimeout': 'Receive timeout',
|
|
31
|
+
'adapter.dispatchFailure': 'Cannot dispatch message to bus',
|
|
32
|
+
'adapter.methodNotFound': 'Method {method} not found',
|
|
33
|
+
'adapter.queueNotFound': 'Queue not found',
|
|
34
|
+
'adapter.invalidPullStream': 'Invalid pull stream',
|
|
35
|
+
'adapter.paramsValidation': 'Method {method} parameters failed validation for: {fields}',
|
|
36
|
+
'adapter.resultValidation': 'Method {method} result failed validation for: {fields}',
|
|
37
|
+
'adapter.deadlock':
|
|
38
|
+
'Method {method} was recursively called, which may cause a deadlock!\nx-b3-traceid: {traceId}\nx-ut-stack: {sequence}',
|
|
39
|
+
'adapter.noMeta': '$meta not passed',
|
|
40
|
+
'adapter.noMetaForward': '$meta.forward not passed to method {method}',
|
|
41
|
+
'adapter.noTraceId': "$meta.forward['x-b3-traceid'] not passed to method {method}",
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
let _errors: Errors<typeof errorMap>;
|
|
45
|
+
|
|
46
|
+
const reserved: string[] = [
|
|
47
|
+
'reducer',
|
|
48
|
+
'start',
|
|
49
|
+
'stop',
|
|
50
|
+
'ready',
|
|
51
|
+
'init',
|
|
52
|
+
'namespace',
|
|
53
|
+
'send',
|
|
54
|
+
'requestSend',
|
|
55
|
+
'responseSend',
|
|
56
|
+
'errorSend',
|
|
57
|
+
'receive',
|
|
58
|
+
'requestReceive',
|
|
59
|
+
'responseReceive',
|
|
60
|
+
'errorReceive',
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* AdapterBase — the runtime-provided base class for all adapters.
|
|
65
|
+
*
|
|
66
|
+
* This class replaces the plain-object literal that was previously defined
|
|
67
|
+
* inline in `adapter()`. It is exposed through the runtime injection
|
|
68
|
+
* mechanism — realms and solutions never import it directly. Custom
|
|
69
|
+
* adapters extend it via prototype-chain inheritance
|
|
70
|
+
* (`Object.setPrototypeOf(current, baseInstance)`), preserving the existing
|
|
71
|
+
* hot-reload semantics.
|
|
72
|
+
*/
|
|
73
|
+
export class AdapterBase<T = Record<string, unknown>, C = Record<string, unknown>> {
|
|
74
|
+
errors = _errors;
|
|
75
|
+
exec: unknown = null;
|
|
76
|
+
imported: object = {};
|
|
77
|
+
config: Config<T, C> = {} as Config<T, C>;
|
|
78
|
+
configBase: string;
|
|
79
|
+
log: unknown = null;
|
|
80
|
+
importedMap?: Map<string, object>;
|
|
81
|
+
|
|
82
|
+
// These are prefixed with _ rather than using # private class fields.
|
|
83
|
+
// The adapter uses Object.setPrototypeOf(current, base) to set the base as
|
|
84
|
+
// the prototype of the plain handler-result object. When prototype methods
|
|
85
|
+
// are then called on `current`, `this` is `current` (not `base`), so
|
|
86
|
+
// JavaScript's brand-checking on # private fields throws. Regular
|
|
87
|
+
// properties are found via normal prototype-chain lookup, which works.
|
|
88
|
+
_register: IApi['register'];
|
|
89
|
+
_subscribe: IApi['subscribe'];
|
|
90
|
+
_dispatch: IApi['dispatch'];
|
|
91
|
+
_methodId: IApi['methodId'];
|
|
92
|
+
_getPath: IApi['getPath'];
|
|
93
|
+
// _api is stored (not _attachHandlers directly) because Registry.ts sets
|
|
94
|
+
// api.attachHandlers = ... only *after* the port factory returns. Storing
|
|
95
|
+
// the api object and reading api.attachHandlers lazily (at call time)
|
|
96
|
+
// ensures we always see the real function rather than the initial undefined.
|
|
97
|
+
_api: Pick<IApi, 'attachHandlers'>;
|
|
98
|
+
_createLog: IApi['createLog'];
|
|
99
|
+
_attachCheckpoint: IApi['attachCheckpoint'];
|
|
100
|
+
_activationNames: string[];
|
|
101
|
+
_queue: PQueue;
|
|
102
|
+
_portLoop: any; // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
103
|
+
_resolveConnected: (value: boolean) => void;
|
|
104
|
+
_connected: Promise<boolean>;
|
|
105
|
+
|
|
106
|
+
constructor(
|
|
107
|
+
api: Pick<
|
|
108
|
+
IApi,
|
|
109
|
+
| 'register'
|
|
110
|
+
| 'subscribe'
|
|
111
|
+
| 'dispatch'
|
|
112
|
+
| 'methodId'
|
|
113
|
+
| 'getPath'
|
|
114
|
+
| 'attachHandlers'
|
|
115
|
+
| 'createLog'
|
|
116
|
+
| 'attachCheckpoint'
|
|
117
|
+
>,
|
|
118
|
+
configBase: string,
|
|
119
|
+
activationNames: string[] = [],
|
|
120
|
+
) {
|
|
121
|
+
this.configBase = configBase;
|
|
122
|
+
this._register = api.register;
|
|
123
|
+
this._subscribe = api.subscribe;
|
|
124
|
+
this._dispatch = api.dispatch;
|
|
125
|
+
this._methodId = api.methodId;
|
|
126
|
+
this._getPath = api.getPath;
|
|
127
|
+
this._api = api;
|
|
128
|
+
this._createLog = api.createLog;
|
|
129
|
+
this._attachCheckpoint = api.attachCheckpoint;
|
|
130
|
+
this._activationNames = activationNames;
|
|
131
|
+
let resolveConnected: (value: boolean) => void;
|
|
132
|
+
this._connected = new Promise<boolean>(resolve => {
|
|
133
|
+
resolveConnected = resolve;
|
|
134
|
+
});
|
|
135
|
+
this._resolveConnected = resolveConnected;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
activeConfig(): object {
|
|
139
|
+
return ConfigRuntime.mergeActivationConfig(this, this._activationNames);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async init(...configs: object[]): Promise<void> {
|
|
143
|
+
this.config = merge(this.activeConfig(), ...configs) as Config<T, C>;
|
|
144
|
+
this.log = this._createLog?.(this.config.logLevel || 'info', {
|
|
145
|
+
...this.config.log,
|
|
146
|
+
name: this.config.id,
|
|
147
|
+
context: this.config.type ?? 'dispatch',
|
|
148
|
+
});
|
|
149
|
+
const id = this.config.id.replace(/\./g, '-');
|
|
150
|
+
this._queue = new PQueue({concurrency: this.config.concurrency || 100});
|
|
151
|
+
this._register(
|
|
152
|
+
{
|
|
153
|
+
[`${id}.start`]: this.start.bind(this),
|
|
154
|
+
[`${id}.stop`]: this.stop.bind(this),
|
|
155
|
+
},
|
|
156
|
+
'ports',
|
|
157
|
+
this.config.id,
|
|
158
|
+
this.config.pkg,
|
|
159
|
+
);
|
|
160
|
+
this._subscribe(
|
|
161
|
+
{
|
|
162
|
+
[`${id}.drain`]: this.drain.bind(this),
|
|
163
|
+
},
|
|
164
|
+
'ports',
|
|
165
|
+
this.config.id,
|
|
166
|
+
this.config.pkg,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
error(error: ITypedError, $meta: IMeta): void {
|
|
171
|
+
if ((this.log as {error?: (...args: unknown[]) => void})?.error) {
|
|
172
|
+
if (error.type && $meta?.expect?.includes?.(error.type)) return;
|
|
173
|
+
if ($meta) error.method = $meta.method;
|
|
174
|
+
(this.log as {error: (...args: unknown[]) => void}).error(error);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
findValidation($meta: IMeta): unknown {
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
handles(name: string): boolean {
|
|
183
|
+
if (reserved.includes(name)) return true;
|
|
184
|
+
const id = this.config.id.replace(/\./g, '-');
|
|
185
|
+
return []
|
|
186
|
+
.concat(this.config.namespace || this.config.imports || id)
|
|
187
|
+
.some(namespace => name.startsWith(namespace));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
methodPath(methodName: string): string {
|
|
191
|
+
const afterSlash = methodName.split('/', 2)[1];
|
|
192
|
+
if (afterSlash !== undefined) return afterSlash;
|
|
193
|
+
const strip = this.config?.stripNamespace;
|
|
194
|
+
if (strip) return methodName.split('.').slice(strip).join('.');
|
|
195
|
+
return methodName;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
getConversion($meta: IMeta, type: 'send' | 'receive'): {fn: unknown; name: string} {
|
|
199
|
+
let fn;
|
|
200
|
+
let name: string;
|
|
201
|
+
if ($meta) {
|
|
202
|
+
if ($meta.method) {
|
|
203
|
+
const path = this._getPath($meta.method);
|
|
204
|
+
name = [path, $meta.mtid, type].join('.');
|
|
205
|
+
fn = this.findHandler(name);
|
|
206
|
+
if (!fn) {
|
|
207
|
+
name = [this.methodPath(path), $meta.mtid, type].join('.');
|
|
208
|
+
fn = this.findHandler(name);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (!fn) {
|
|
212
|
+
name = [$meta.opcode, $meta.mtid, type].join('.');
|
|
213
|
+
fn = this.findHandler(name);
|
|
214
|
+
}
|
|
215
|
+
if (!fn) {
|
|
216
|
+
name = [$meta.mtid, type].join('.');
|
|
217
|
+
fn = this.findHandler(name);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
if (!fn && (!$meta || $meta.mtid !== 'event')) {
|
|
221
|
+
name = type;
|
|
222
|
+
fn = this.findHandler(name);
|
|
223
|
+
}
|
|
224
|
+
return {fn, name};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async dispatch(...args: unknown[]): Promise<unknown> {
|
|
228
|
+
const result = this._dispatch(...args);
|
|
229
|
+
if (!result)
|
|
230
|
+
(this.log as {error?: (...args: unknown[]) => void})?.error?.(
|
|
231
|
+
this.errors['adapter.dispatchFailure']({args}),
|
|
232
|
+
);
|
|
233
|
+
return result;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async event(event: string, data?: object, mapper?: string): Promise<unknown> {
|
|
237
|
+
(this.log as {info?: (...args: unknown[]) => void})?.info?.({
|
|
238
|
+
$meta: {mtid: 'event', method: `adapter.${event}`},
|
|
239
|
+
...data,
|
|
240
|
+
});
|
|
241
|
+
const eventHandlers = [];
|
|
242
|
+
this.importedMap?.forEach(
|
|
243
|
+
imp =>
|
|
244
|
+
Object.prototype.hasOwnProperty.call(imp, event) &&
|
|
245
|
+
eventHandlers.push(imp[event]),
|
|
246
|
+
);
|
|
247
|
+
let result: unknown = data;
|
|
248
|
+
switch (mapper) {
|
|
249
|
+
case 'asyncMap':
|
|
250
|
+
result = await Promise.all(
|
|
251
|
+
eventHandlers.map(handler => handler.call(this, data)),
|
|
252
|
+
);
|
|
253
|
+
break;
|
|
254
|
+
case 'reduce':
|
|
255
|
+
default:
|
|
256
|
+
for (const eventHandler of eventHandlers) {
|
|
257
|
+
result = await eventHandler.call(this, result);
|
|
258
|
+
}
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
drain(): void {}
|
|
265
|
+
|
|
266
|
+
findHandler(methodName: string): unknown {
|
|
267
|
+
methodName = this._methodId(methodName);
|
|
268
|
+
return this.imported[methodName];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async request(...params: unknown[]): Promise<unknown> {
|
|
272
|
+
return this._queue.add(this._portLoop(params, true));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async publish(...params: unknown[]): Promise<unknown> {
|
|
276
|
+
await this._queue.add(this._portLoop(params, false));
|
|
277
|
+
return [true, params[params.length - 1]];
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async ready(): Promise<unknown> {
|
|
281
|
+
return this.event('ready');
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
forNamespaces<R>(reducer: (prev: R, current: unknown) => R, initial: R): R {
|
|
285
|
+
const id = this.config.id.replace(/\./g, '-');
|
|
286
|
+
return []
|
|
287
|
+
.concat(this.config.namespace || this.config.imports || id)
|
|
288
|
+
.reduce(reducer.bind(this), initial);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async start(): Promise<unknown> {
|
|
292
|
+
await this._api.attachHandlers(this, this.config.imports, true);
|
|
293
|
+
const {req, pub} = this.forNamespaces(
|
|
294
|
+
(prev, next) => {
|
|
295
|
+
if (typeof next === 'string') {
|
|
296
|
+
prev.req[`${next}.request`] = this.request.bind(this);
|
|
297
|
+
prev.pub[`${next}.publish`] = this.publish.bind(this);
|
|
298
|
+
}
|
|
299
|
+
return prev;
|
|
300
|
+
},
|
|
301
|
+
{req: {}, pub: {}},
|
|
302
|
+
);
|
|
303
|
+
this._register(req, 'ports', this.config.id, this.config.pkg);
|
|
304
|
+
this._subscribe(pub, 'ports', this.config.id, this.config.pkg);
|
|
305
|
+
const {context, ...config} = this.config; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
306
|
+
return this.event('start', {configBase: this.configBase, config});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async link(patterns: unknown, target: {imported?: object} = {}): Promise<object> {
|
|
310
|
+
await this._api.attachHandlers(target, patterns, false);
|
|
311
|
+
return target.imported;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
async handle(...params: unknown[]): Promise<unknown> {
|
|
315
|
+
const $meta = params && params.length > 1 && (params[params.length - 1] as IMeta);
|
|
316
|
+
if ($meta && typeof $meta === 'object') this._attachCheckpoint?.($meta);
|
|
317
|
+
const method = ($meta && $meta.method) || 'exec';
|
|
318
|
+
const handler = this.findHandler(method) || this.imported['exec'];
|
|
319
|
+
if (handler instanceof Function) {
|
|
320
|
+
return handler.apply(this, params);
|
|
321
|
+
} else {
|
|
322
|
+
throw this.errors['adapter.methodNotFound']({params: {method}});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
connect(
|
|
327
|
+
what?: net.Socket | (() => void),
|
|
328
|
+
context?: any, // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
329
|
+
): void {
|
|
330
|
+
what ??= this.handle.bind(this);
|
|
331
|
+
context ??= this.config.context;
|
|
332
|
+
this._portLoop = loop(what, this as any, context); // eslint-disable-line @typescript-eslint/no-explicit-any
|
|
333
|
+
this._resolveConnected(true);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async connected(): Promise<boolean> {
|
|
337
|
+
return this._connected;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
async stop(): Promise<unknown> {
|
|
341
|
+
return this.event('stop');
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Create an adapter instance from the API and handler definitions.
|
|
347
|
+
*
|
|
348
|
+
* The `AdapterBase` class is instantiated here (not imported by realms) and
|
|
349
|
+
* placed at the end of the handler prototype chain. This preserves the
|
|
350
|
+
* constraint that realms/solutions never depend on blong-gogo.
|
|
351
|
+
*/
|
|
352
|
+
export default async function adapter<T, C>(
|
|
353
|
+
api: IApi,
|
|
354
|
+
configBase: string,
|
|
355
|
+
activationNames: string[] = [],
|
|
356
|
+
): Promise<ReturnType<IAdapterFactory>> {
|
|
357
|
+
const {adapter: adapterFactory, utError, handlers, remote, rpc, local, registry, type} = api;
|
|
358
|
+
_errors ||= utError.register(errorMap);
|
|
359
|
+
|
|
360
|
+
const base = new AdapterBase<T, C>(api, configBase, activationNames);
|
|
361
|
+
|
|
362
|
+
const result = handlers({utError, remote, type});
|
|
363
|
+
let current = result;
|
|
364
|
+
while (current.extends) {
|
|
365
|
+
const parent = await (typeof current.extends === 'string'
|
|
366
|
+
? adapterFactory(current.extends)({utError, remote, rpc, local, registry})
|
|
367
|
+
: current.extends({utError, remote, rpc, local, registry}));
|
|
368
|
+
Object.setPrototypeOf(current, parent);
|
|
369
|
+
current = parent;
|
|
370
|
+
}
|
|
371
|
+
Object.setPrototypeOf(current, base);
|
|
372
|
+
|
|
373
|
+
return result as ReturnType<IAdapterFactory>;
|
|
374
|
+
}
|
package/src/ApiSchema.ts
CHANGED
|
@@ -2,17 +2,15 @@ import type {
|
|
|
2
2
|
GatewaySchema,
|
|
3
3
|
IApiSchema,
|
|
4
4
|
ILog,
|
|
5
|
+
IPlatformApi,
|
|
5
6
|
PathItemObject,
|
|
6
7
|
SchemaObject,
|
|
7
8
|
} from '@feasibleone/blong/types';
|
|
8
9
|
import {Internal} from '@feasibleone/blong/types';
|
|
9
|
-
import {
|
|
10
|
-
import path, {basename, dirname, extname} from 'node:path';
|
|
10
|
+
import {type Dirent} from 'node:fs';
|
|
11
11
|
|
|
12
|
-
import {join} from 'path';
|
|
13
12
|
import {identifier} from './lib.ts';
|
|
14
13
|
import loadApi from './loadApi.ts';
|
|
15
|
-
import scan from './scan.ts';
|
|
16
14
|
|
|
17
15
|
interface IConfig {
|
|
18
16
|
logLevel?: Parameters<ILog['logger']>[0];
|
|
@@ -24,6 +22,7 @@ export default class ApiSchema extends Internal implements IApiSchema {
|
|
|
24
22
|
logLevel: 'debug',
|
|
25
23
|
generate: true,
|
|
26
24
|
};
|
|
25
|
+
#platform: IPlatformApi;
|
|
27
26
|
|
|
28
27
|
#loaded: Record<string, GatewaySchema> = {};
|
|
29
28
|
#namespace: Record<string, Record<string, GatewaySchema>> = {};
|
|
@@ -36,8 +35,9 @@ export default class ApiSchema extends Internal implements IApiSchema {
|
|
|
36
35
|
}
|
|
37
36
|
> = {};
|
|
38
37
|
|
|
39
|
-
public constructor(config: IConfig, {log}: {log: ILog}) {
|
|
38
|
+
public constructor(config: IConfig, {log, platform}: {log: ILog; platform: IPlatformApi}) {
|
|
40
39
|
super({log});
|
|
40
|
+
this.#platform = platform;
|
|
41
41
|
this.merge(this.#config, config);
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -45,6 +45,13 @@ export default class ApiSchema extends Internal implements IApiSchema {
|
|
|
45
45
|
return operation?.['x-blong-method'] || operation.operationId;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
+
public loadApi(
|
|
49
|
+
locations: string | string[] | object | object[] | {assets: object},
|
|
50
|
+
source: string = process.cwd(),
|
|
51
|
+
): ReturnType<typeof loadApi> {
|
|
52
|
+
return loadApi(locations, source, this.#platform);
|
|
53
|
+
}
|
|
54
|
+
|
|
48
55
|
public async schema(
|
|
49
56
|
{
|
|
50
57
|
namespace,
|
|
@@ -57,8 +64,8 @@ export default class ApiSchema extends Internal implements IApiSchema {
|
|
|
57
64
|
): Promise<Record<string, GatewaySchema>> {
|
|
58
65
|
const result: Record<string, GatewaySchema> = {};
|
|
59
66
|
if (url) {
|
|
60
|
-
const dir = dirname(url.startsWith('file://') ? url.slice(7) : url);
|
|
61
|
-
const files = await scan(dir);
|
|
67
|
+
const dir = this.#platform.dirname(url.startsWith('file://') ? url.slice(7) : url);
|
|
68
|
+
const files = await this.#platform.scan(dir);
|
|
62
69
|
namespace = namespace || {};
|
|
63
70
|
for (const file of files) {
|
|
64
71
|
if (
|
|
@@ -67,9 +74,9 @@ export default class ApiSchema extends Internal implements IApiSchema {
|
|
|
67
74
|
file.name.endsWith('.yml') ||
|
|
68
75
|
file.name.endsWith('.json'))
|
|
69
76
|
) {
|
|
70
|
-
const [name] = basename(file.name).split('.');
|
|
77
|
+
const [name] = this.#platform.basename(file.name).split('.');
|
|
71
78
|
namespace[name] ||= [];
|
|
72
|
-
namespace[name].push(join(dir, file.name));
|
|
79
|
+
namespace[name].push(this.#platform.join(dir, file.name));
|
|
73
80
|
}
|
|
74
81
|
}
|
|
75
82
|
}
|
|
@@ -83,7 +90,7 @@ export default class ApiSchema extends Internal implements IApiSchema {
|
|
|
83
90
|
);
|
|
84
91
|
|
|
85
92
|
for (const [name, locations] of Object.entries(namespace)) {
|
|
86
|
-
const bundle = await loadApi(locations, source);
|
|
93
|
+
const bundle = await loadApi(locations, source, this.#platform);
|
|
87
94
|
const {namespace = name, destination} = bundle['x-blong'] ?? {};
|
|
88
95
|
this.#namespace[namespace] ||= {};
|
|
89
96
|
Object.entries(bundle.paths).forEach(([path, methods]: [string, PathItemObject]) => {
|
|
@@ -132,17 +139,17 @@ export default class ApiSchema extends Internal implements IApiSchema {
|
|
|
132
139
|
for (const [method, operation] of Object.entries(this.#loaded)) {
|
|
133
140
|
const filename = operation.subject + this.method(operation.operation) + '.ts';
|
|
134
141
|
if (method.startsWith(prefix) && !record.existing.has(filename.toLowerCase())) {
|
|
135
|
-
generate.push(
|
|
142
|
+
generate.push(this.#platform.join(record.dir, filename));
|
|
136
143
|
}
|
|
137
144
|
}
|
|
138
145
|
}
|
|
139
146
|
for (const filename of generate.concat(Array.from(this.#generateFile))) {
|
|
140
|
-
const method = basename(filename, extname(filename));
|
|
147
|
+
const method = this.#platform.basename(filename, this.#platform.extname(filename));
|
|
141
148
|
const schema = this.#loaded[method.toLowerCase()];
|
|
142
149
|
if (schema) {
|
|
143
150
|
// console.log(schema.operation.responses);
|
|
144
151
|
this.log?.warn?.(`Writing ${filename}`);
|
|
145
|
-
writeFileSync(
|
|
152
|
+
this.#platform.writeFileSync(
|
|
146
153
|
filename,
|
|
147
154
|
`import unchanged from '@feasibleone/blong';
|
|
148
155
|
import {type IMeta, handler} from '@feasibleone/blong/types';
|
|
@@ -256,16 +263,8 @@ export default handler(
|
|
|
256
263
|
|
|
257
264
|
public async generateFile(filename: string): Promise<boolean> {
|
|
258
265
|
if (this.#config.generate === false) return false;
|
|
259
|
-
if (statSync(filename).size !== 0) return false;
|
|
260
|
-
let content = '';
|
|
261
|
-
await new Promise<void>((resolve, reject) => {
|
|
262
|
-
createReadStream(filename, {end: 50, encoding: 'utf-8'})
|
|
263
|
-
.on('data', chunk => {
|
|
264
|
-
content += chunk;
|
|
265
|
-
})
|
|
266
|
-
.on('error', reject)
|
|
267
|
-
.on('close', resolve);
|
|
268
|
-
});
|
|
266
|
+
if (this.#platform.statSync(filename).size !== 0) return false;
|
|
267
|
+
let content = this.#platform.readFileSync(filename, {encoding: 'utf-8'}).toString('utf-8');
|
|
269
268
|
if (content.includes('import unchanged from')) {
|
|
270
269
|
this.#generateFile.add(filename);
|
|
271
270
|
return false;
|
|
@@ -288,6 +287,8 @@ export default handler(
|
|
|
288
287
|
existing: new Set(),
|
|
289
288
|
};
|
|
290
289
|
}
|
|
291
|
-
files.forEach(file =>
|
|
290
|
+
files.forEach(file =>
|
|
291
|
+
record.existing.add(this.#platform.basename(file.name).toLowerCase()),
|
|
292
|
+
);
|
|
292
293
|
}
|
|
293
294
|
}
|