@feasibleone/blong-gogo 1.15.0 → 1.17.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 +24 -0
- package/bin/blong-dev.ts +2 -41
- package/bin/blong.ts +2 -42
- package/package.json +14 -3
- package/src/BrowserLog.ts +144 -0
- package/src/ConfigRuntime.test.ts +363 -0
- package/src/ConfigRuntime.ts +384 -0
- package/src/Gateway.ts +6 -2
- package/src/Realm.ts +21 -5
- package/src/Registry.ts +38 -1
- package/src/Remote.ts +1 -1
- package/src/RpcClient.ts +7 -1
- package/src/RpcServer.ts +16 -7
- package/src/Watch.ts +97 -4
- package/src/adapter/browser/http.ts +63 -36
- package/src/adapter/server/http.ts +21 -0
- package/src/adapter/server/knex.ts +18 -0
- package/src/adapter.ts +25 -2
- package/src/browser.ts +4 -0
- package/src/chain.ts +5 -5
- package/src/checkpoint.ts +32 -0
- package/src/codec/adapter/jsonrpc/receive.ts +6 -1
- package/src/codec/adapter/openapi/ready.ts +8 -3
- package/src/codec/test/test/testLoginTokenCreate.ts +12 -14
- package/src/folderAnalysis.ts +293 -0
- package/src/jose.ts +47 -5
- package/src/layerProxy.ts +277 -26
- package/src/lib.test.ts +102 -0
- package/src/lib.ts +36 -0
- package/src/load.ts +141 -81
- package/src/loadApi.ts +6 -1
- package/src/loadServer.ts +73 -0
- package/src/mle.ts +2 -0
- package/src/orchestrator/common/dispatch.ts +8 -3
- package/src/orchestrator/common/openapi.ts +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.17.0](https://github.com/feasibleone/blong/compare/blong-gogo-v1.16.0...blong-gogo-v1.17.0) (2026-04-10)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* auto-discover and run blong from a folder with loose handler files ([#120](https://github.com/feasibleone/blong/issues/120)) ([4b407b6](https://github.com/feasibleone/blong/commit/4b407b6d0888eed0bb6cdc8428d82ed15f5152c5))
|
|
9
|
+
* **blong-gogo:** browser compatibility — replace got/tls with ky+undici, add BrowserLog, static tree-shaking split ([#123](https://github.com/feasibleone/blong/issues/123)) ([91b8d1a](https://github.com/feasibleone/blong/commit/91b8d1a159463f9e5a0f4f6d52c4c285989cac43))
|
|
10
|
+
* blong-ui ([9ca1281](https://github.com/feasibleone/blong/commit/9ca1281c0178c69dfc722bc4a5978f649bc1fa0d))
|
|
11
|
+
* improve logging ([5f43391](https://github.com/feasibleone/blong/commit/5f43391a43a99e0dd591401e24c45cd02b3bdba2))
|
|
12
|
+
* simplify api loading ([e6ff342](https://github.com/feasibleone/blong/commit/e6ff34239d7f1f53e3a21d30768ed5ab45042786))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* bin deduplicate ([af41171](https://github.com/feasibleone/blong/commit/af41171c0c4dbb0599f7bbe1c82efc4923fd79c8))
|
|
18
|
+
|
|
19
|
+
## [1.16.0](https://github.com/feasibleone/blong/compare/blong-gogo-v1.15.0...blong-gogo-v1.16.0) (2026-04-01)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Features
|
|
23
|
+
|
|
24
|
+
* implement ConfigRuntime for hot configuration reload ([#115](https://github.com/feasibleone/blong/issues/115)) ([61bab7c](https://github.com/feasibleone/blong/commit/61bab7ce8587bf83d72fe80138aaef68943f21c4))
|
|
25
|
+
* unified test and handlers ([#117](https://github.com/feasibleone/blong/issues/117)) ([bf0ed96](https://github.com/feasibleone/blong/commit/bf0ed96c5df3d949fa225dd8a30fc25698a7855a))
|
|
26
|
+
|
|
3
27
|
## [1.15.0](https://github.com/feasibleone/blong/compare/blong-gogo-v1.14.3...blong-gogo-v1.15.0) (2026-03-29)
|
|
4
28
|
|
|
5
29
|
|
package/bin/blong-dev.ts
CHANGED
|
@@ -1,47 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env -S node --watch --inspect
|
|
2
2
|
|
|
3
3
|
import minimist from 'minimist';
|
|
4
|
-
import {
|
|
5
|
-
import {basename, resolve} from 'node:path';
|
|
6
|
-
import load from '../src/load.ts';
|
|
4
|
+
import {autoRun} from '../src/loadServer.ts';
|
|
7
5
|
|
|
8
6
|
const argv: {_: string[]} = minimist(process.argv.slice(2));
|
|
9
|
-
const cwd = process.cwd();
|
|
10
|
-
const target = argv._[0];
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
// Explicit file provided — load it directly (existing behavior)
|
|
14
|
-
(await import(resolve(target))).default(load);
|
|
15
|
-
} else {
|
|
16
|
-
// Auto-detect what to run based on available files in the current directory.
|
|
17
|
-
const indexFile = resolve(cwd, 'index.ts');
|
|
18
|
-
const serverFile = resolve(cwd, 'server.ts');
|
|
19
|
-
const browserFile = resolve(cwd, 'browser.ts');
|
|
20
|
-
|
|
21
|
-
if (existsSync(indexFile)) {
|
|
22
|
-
(await import(indexFile)).default(load);
|
|
23
|
-
} else if (existsSync(serverFile) && existsSync(browserFile)) {
|
|
24
|
-
const {default: server} = await import(serverFile);
|
|
25
|
-
const {default: browser} = await import(browserFile);
|
|
26
|
-
const name = basename(cwd);
|
|
27
|
-
const platforms: Awaited<ReturnType<typeof load>>[] = await Promise.all([
|
|
28
|
-
load(server, name, name, ['microservice', 'integration', 'dev']),
|
|
29
|
-
load(browser, name, name, ['microservice', 'integration', 'dev']),
|
|
30
|
-
]);
|
|
31
|
-
for (const platform of platforms) await platform.start();
|
|
32
|
-
await platforms[1].test();
|
|
33
|
-
if (process.env.CI) for (const platform of platforms) await platform.stop();
|
|
34
|
-
} else if (existsSync(serverFile)) {
|
|
35
|
-
const {default: server} = await import(serverFile);
|
|
36
|
-
const name = basename(cwd);
|
|
37
|
-
const platform = await load(server, name, name, ['microservice', 'integration', 'dev']);
|
|
38
|
-
await platform.start();
|
|
39
|
-
await platform.test();
|
|
40
|
-
if (process.env.CI) await platform.stop();
|
|
41
|
-
} else {
|
|
42
|
-
throw new Error(
|
|
43
|
-
`No index.ts, server.ts, or browser.ts found in ${cwd}. ` +
|
|
44
|
-
'Run blong from a suite or realm folder, or provide a file path.',
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
8
|
+
await autoRun({cwd: process.cwd(), target: argv._[0]});
|
package/bin/blong.ts
CHANGED
|
@@ -1,48 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env -S node
|
|
2
2
|
|
|
3
3
|
import minimist from 'minimist';
|
|
4
|
-
import {
|
|
5
|
-
import { basename, join, resolve } from 'node:path';
|
|
6
|
-
import load from '../src/load.ts';
|
|
4
|
+
import {autoRun} from '../src/loadServer.ts';
|
|
7
5
|
|
|
8
6
|
const argv: {_: string[]} = minimist(process.argv.slice(2));
|
|
9
|
-
const cwd = process.cwd();
|
|
10
|
-
const target = argv._[0];
|
|
11
7
|
|
|
12
|
-
|
|
13
|
-
// Explicit file provided — load it directly
|
|
14
|
-
(await import(resolve(target))).default(load);
|
|
15
|
-
} else {
|
|
16
|
-
const indexFile = join(cwd, 'index.ts');
|
|
17
|
-
const serverFile = join(cwd, 'server.ts');
|
|
18
|
-
const browserFile = join(cwd, 'browser.ts');
|
|
19
|
-
const name = basename(cwd);
|
|
20
|
-
|
|
21
|
-
if (existsSync(indexFile)) {
|
|
22
|
-
// index.ts exists — use it directly (suite or realm with a custom runner)
|
|
23
|
-
(await import(indexFile)).default(load);
|
|
24
|
-
} else if (existsSync(serverFile) && existsSync(browserFile)) {
|
|
25
|
-
// Both server.ts and browser.ts — two-platform (suite or realm with browser)
|
|
26
|
-
const {default: serverDef} = await import(serverFile);
|
|
27
|
-
const {default: browserDef} = await import(browserFile);
|
|
28
|
-
const platforms: Awaited<ReturnType<typeof load>>[] = await Promise.all([
|
|
29
|
-
load(serverDef, name, name, ['microservice', 'integration', 'dev']),
|
|
30
|
-
load(browserDef, name, name, ['microservice', 'integration', 'dev']),
|
|
31
|
-
]);
|
|
32
|
-
for (const platform of platforms) await platform.start();
|
|
33
|
-
await platforms[1].test();
|
|
34
|
-
if (process.env.CI) for (const platform of platforms) await platform.stop();
|
|
35
|
-
} else if (existsSync(serverFile)) {
|
|
36
|
-
// Only server.ts — suite or realm (loadRealm detects and wraps realms automatically)
|
|
37
|
-
const {default: serverDef} = await import(serverFile);
|
|
38
|
-
const platform = await load(serverDef, name, name, ['microservice', 'integration', 'dev']);
|
|
39
|
-
await platform.start();
|
|
40
|
-
await platform.test();
|
|
41
|
-
if (process.env.CI) await platform.stop();
|
|
42
|
-
} else {
|
|
43
|
-
throw new Error(
|
|
44
|
-
`No index.ts or server.ts found in ${cwd}. ` +
|
|
45
|
-
'Run blong from a suite or realm folder, or provide a file path.',
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
8
|
+
await autoRun({cwd: process.cwd(), target: argv._[0]});
|
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feasibleone/blong-gogo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.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/browser.ts",
|
|
11
|
+
"import": "./src/load.ts"
|
|
12
|
+
},
|
|
13
|
+
"./browser": "./src/adapter/browser.ts",
|
|
14
|
+
"./ConfigRuntime.js": "./src/ConfigRuntime.ts"
|
|
10
15
|
},
|
|
11
16
|
"bin": {
|
|
12
17
|
"blong": "./bin/blong.ts",
|
|
@@ -39,6 +44,7 @@
|
|
|
39
44
|
"glob": "^13.0.6",
|
|
40
45
|
"got": "^14.6.6",
|
|
41
46
|
"jose": "^6.2.1",
|
|
47
|
+
"undici": "^8.0.1",
|
|
42
48
|
"knex": "^3.1.0",
|
|
43
49
|
"ky": "^1.14.2",
|
|
44
50
|
"lru-cache": "^11.2.4",
|
|
@@ -69,11 +75,16 @@
|
|
|
69
75
|
"@rushstack/heft": "^1.2.6",
|
|
70
76
|
"@rushstack/heft-lint-plugin": "^1.2.6",
|
|
71
77
|
"@rushstack/heft-typescript-plugin": "^1.3.1",
|
|
78
|
+
"@types/node": "^24",
|
|
72
79
|
"eslint": "~9.39.2",
|
|
80
|
+
"playwright": "^1.58.2",
|
|
81
|
+
"tap": "^21.6.2",
|
|
73
82
|
"typescript": "^5.9.3"
|
|
74
83
|
},
|
|
75
84
|
"scripts": {
|
|
76
85
|
"build": "true",
|
|
77
|
-
"
|
|
86
|
+
"browser-check": "node scripts/browser-compat-check.mjs",
|
|
87
|
+
"ci-publish": "node ../../common/scripts/install-run-rush-pnpm.js publish --access public --provenance",
|
|
88
|
+
"ci-unit": "tap src/ConfigRuntime.test.ts src/lib.test.ts --allow-incomplete-coverage"
|
|
78
89
|
}
|
|
79
90
|
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import {Internal, type ILog} from '@feasibleone/blong/types';
|
|
2
|
+
import {pino, type Level, type Logger, type LoggerOptions} from 'pino';
|
|
3
|
+
|
|
4
|
+
// ── level constants (pino uses numeric levels matching bunyan) ──────────────
|
|
5
|
+
const TRACE = 10, DEBUG = 20, INFO = 30, WARN = 40, ERROR = 50, FATAL = 60;
|
|
6
|
+
|
|
7
|
+
const nameFromLevel: Record<number, string> = {
|
|
8
|
+
[TRACE]: 'trace', [DEBUG]: 'debug', [INFO]: 'info',
|
|
9
|
+
[WARN]: 'warn', [ERROR]: 'error', [FATAL]: 'fatal',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const LEVEL_CSS: Record<string, string> = {
|
|
13
|
+
trace: 'color: grey',
|
|
14
|
+
debug: 'color: blue',
|
|
15
|
+
info: 'color: cyan',
|
|
16
|
+
warn: 'color: magenta',
|
|
17
|
+
error: 'color: red',
|
|
18
|
+
fatal: 'color: red; font-weight: bold',
|
|
19
|
+
};
|
|
20
|
+
const DEFAULT_CSS = {
|
|
21
|
+
def: 'color: black',
|
|
22
|
+
msg: 'color: darkblue',
|
|
23
|
+
service: 'color: darkorange',
|
|
24
|
+
mtid: 'color: Magenta',
|
|
25
|
+
src: 'color: DimGray; font-style: italic; font-size: 0.9em',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Fields written separately; everything else goes into the `details` object
|
|
29
|
+
const SKIP = new Set([
|
|
30
|
+
'name', 'hostname', 'pid', 'level', 'component', 'msg', 'time', 'v',
|
|
31
|
+
'src', 'error', 'clientReq', 'clientRes', 'req', 'res',
|
|
32
|
+
'$meta', 'mtid', 'jsException', 'service',
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
export interface IBrowserLogConfig {
|
|
36
|
+
level?: Level;
|
|
37
|
+
/** Route each log call to the matching console.warn / console.error etc.
|
|
38
|
+
* instead of always using console.log. Default: false */
|
|
39
|
+
logByLevel?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function write(rec: Record<string, unknown>, logByLevel: boolean): void {
|
|
43
|
+
const level = rec.level as number;
|
|
44
|
+
const levelKey = nameFromLevel[level] ?? 'info';
|
|
45
|
+
const paddedLevel = levelKey.toUpperCase().padStart(5);
|
|
46
|
+
|
|
47
|
+
// Choose the console method
|
|
48
|
+
let consoleMethod: (...a: unknown[]) => void = console.log; // eslint-disable-line no-console
|
|
49
|
+
if (logByLevel) {
|
|
50
|
+
const mapped = level <= TRACE ? 'debug' : level >= FATAL ? 'error' : levelKey;
|
|
51
|
+
consoleMethod = (typeof (console as Record<string, unknown>)[mapped] === 'function'
|
|
52
|
+
? (console as Record<string, (...a: unknown[]) => void>)[mapped]
|
|
53
|
+
: console.log); // eslint-disable-line no-console
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const levelCss =
|
|
57
|
+
level < DEBUG ? LEVEL_CSS.trace :
|
|
58
|
+
level < INFO ? LEVEL_CSS.debug :
|
|
59
|
+
level < WARN ? LEVEL_CSS.info :
|
|
60
|
+
level < ERROR ? LEVEL_CSS.warn :
|
|
61
|
+
level < FATAL ? LEVEL_CSS.error : LEVEL_CSS.fatal;
|
|
62
|
+
|
|
63
|
+
// any fields not in the skip list become the expandable details object
|
|
64
|
+
const details: Record<string, unknown> = {};
|
|
65
|
+
for (const [k, v] of Object.entries(rec)) {
|
|
66
|
+
if (v != null && !SKIP.has(k)) details[k] = v;
|
|
67
|
+
}
|
|
68
|
+
const hasDetails = Object.keys(details).length > 0;
|
|
69
|
+
|
|
70
|
+
const loggerName = rec.childName
|
|
71
|
+
? `${rec.name}/${rec.childName}`
|
|
72
|
+
: (rec.name as string | undefined) ?? '';
|
|
73
|
+
|
|
74
|
+
const time = rec.time instanceof Date
|
|
75
|
+
? rec.time.toISOString().slice(11, 23)
|
|
76
|
+
: new Date().toISOString().slice(11, 23);
|
|
77
|
+
|
|
78
|
+
const label =
|
|
79
|
+
(rec.$meta as Record<string, string> | undefined)?.method ??
|
|
80
|
+
(rec.$meta as Record<string, string> | undefined)?.opcode ??
|
|
81
|
+
(rec.msg as string | undefined) ?? '';
|
|
82
|
+
|
|
83
|
+
const fmt = `[%s] %c%s%c %s%c %s: %c%s %c%s${hasDetails ? ' %c%o' : ''}`;
|
|
84
|
+
const args: unknown[] = [
|
|
85
|
+
fmt,
|
|
86
|
+
time,
|
|
87
|
+
levelCss, paddedLevel,
|
|
88
|
+
DEFAULT_CSS.service, rec.service ?? '',
|
|
89
|
+
DEFAULT_CSS.def, loggerName,
|
|
90
|
+
DEFAULT_CSS.mtid, (rec.mtid as string | undefined) ?? '',
|
|
91
|
+
DEFAULT_CSS.msg, label,
|
|
92
|
+
];
|
|
93
|
+
if (hasDetails) args.push(DEFAULT_CSS.src, details);
|
|
94
|
+
|
|
95
|
+
consoleMethod(...args);
|
|
96
|
+
|
|
97
|
+
if (rec.error && (rec.error as {stack?: string}).stack)
|
|
98
|
+
console.error(rec.error); // eslint-disable-line no-console
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export default class BrowserLog extends Internal implements ILog {
|
|
102
|
+
#logger: Logger;
|
|
103
|
+
#config: IBrowserLogConfig = {level: 'info', logByLevel: false};
|
|
104
|
+
|
|
105
|
+
public constructor(config: IBrowserLogConfig) {
|
|
106
|
+
super();
|
|
107
|
+
this.merge(this.#config, config);
|
|
108
|
+
const logByLevel = this.#config.logByLevel ?? false;
|
|
109
|
+
this.#logger = pino({
|
|
110
|
+
level: this.#config.level ?? 'info',
|
|
111
|
+
browser: {
|
|
112
|
+
asObject: true,
|
|
113
|
+
write: (rec: Record<string, unknown>) => write(rec, logByLevel),
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public child<T extends string>(...params: Parameters<Logger<never>['child']>): Logger<T> {
|
|
119
|
+
return this.#logger.child(...params) as Logger<T>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public logger(
|
|
123
|
+
level: LoggerOptions['level'] = this.#config.level,
|
|
124
|
+
bindings: object,
|
|
125
|
+
): ReturnType<ILog['logger']> {
|
|
126
|
+
const child = this.#logger.child(bindings, {level});
|
|
127
|
+
const result = {trace: null, debug: null, info: null, warn: null, error: null, fatal: null};
|
|
128
|
+
switch (level) {
|
|
129
|
+
case 'trace':
|
|
130
|
+
result.trace = child.trace.bind(child);
|
|
131
|
+
case 'debug': // eslint-disable-line no-fallthrough
|
|
132
|
+
result.debug = child.debug.bind(child);
|
|
133
|
+
case 'info': // eslint-disable-line no-fallthrough
|
|
134
|
+
result.info = child.info.bind(child);
|
|
135
|
+
case 'warn': // eslint-disable-line no-fallthrough
|
|
136
|
+
result.warn = child.warn.bind(child);
|
|
137
|
+
case 'error': // eslint-disable-line no-fallthrough
|
|
138
|
+
result.error = child.error.bind(child);
|
|
139
|
+
case 'fatal': // eslint-disable-line no-fallthrough
|
|
140
|
+
result.fatal = child.fatal.bind(child);
|
|
141
|
+
}
|
|
142
|
+
return result;
|
|
143
|
+
}
|
|
144
|
+
}
|