@feasibleone/blong-gogo 1.25.0 → 1.26.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 +44 -0
- package/bin/blong-watch.ts +1 -1
- package/package.json +15 -9
- package/src/AdapterBase.ts +26 -12
- package/src/ConfigRuntime.ts +20 -19
- package/src/Gateway.ts +63 -3
- package/src/Registry.ts +39 -61
- package/src/ResolutionLocal.ts +19 -6
- package/src/RpcServer.ts +13 -2
- package/src/Watch.ts +35 -6
- package/src/adapter/browser/http.ts +45 -5
- package/src/adapter/schema/knex/binary.ts +194 -0
- package/src/adapter/schema/knex/schemaMysql.ts +208 -0
- package/src/adapter/schema/knex/schemaTable.ts +302 -0
- package/src/adapter/schema/knex/types.ts +97 -0
- package/src/adapter/schema/knex/utils.ts +193 -0
- package/src/adapter/server/knex.ts +569 -58
- package/src/codec/adapter/mle/ready.ts +9 -4
- package/src/codec/adapter/openapi/ready.ts +1 -3
- package/src/folderAnalysis.ts +0 -1
- package/src/handlerProxy.ts +27 -50
- package/src/jose.ts +11 -5
- package/src/jwt.ts +88 -5
- package/src/layerProxy.ts +81 -18
- package/src/load.ts +252 -101
- package/src/loadBrowser.ts +20 -3
- package/src/loadServer.ts +25 -7
- package/src/mle.ts +34 -8
- package/src/orchestrator/common/openapi.ts +1 -3
- package/src/runServer.test.ts +6 -9
- package/src/runServer.ts +39 -10
- package/src/adapter/schema/schemaCrudBind.ts +0 -174
- package/src/adapter/schema/schemaProcedureBind.ts +0 -129
- package/src/adapter/schema/schemaProcedureSync.ts +0 -37
- package/src/adapter/schema/schemaTableSync.ts +0 -112
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,49 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.26.0](https://github.com/feasibleone/blong/compare/blong-gogo-v1.25.1...blong-gogo-v1.26.0) (2026-07-14)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* authorization ([cf1a7ff](https://github.com/feasibleone/blong/commit/cf1a7ff605de0a6078fc859a31532443386808b2))
|
|
9
|
+
* blong-mock, blong-server ([756caca](https://github.com/feasibleone/blong/commit/756cacab712db6792bf1b93830fc1d833b396f8d))
|
|
10
|
+
* blong-party ([602a3b4](https://github.com/feasibleone/blong/commit/602a3b422e1f76c27a96b1cd5d5a8cf0edbc268f))
|
|
11
|
+
* check access credentials ([e6af405](https://github.com/feasibleone/blong/commit/e6af405c43921cb9c553db9c4986b7fe56154724))
|
|
12
|
+
* core.resource merging ([371d3e2](https://github.com/feasibleone/blong/commit/371d3e2b3f519dfd59ca4fd480bc33317992afcd))
|
|
13
|
+
* implement subjectObjectSchema ([8ca3282](https://github.com/feasibleone/blong/commit/8ca32829277b9816eb5e43ce708d2f1d0cc3ef28))
|
|
14
|
+
* improve playwright ([c88dca8](https://github.com/feasibleone/blong/commit/c88dca832548a878ce81aab7320395c3c6a81551))
|
|
15
|
+
* manifest concept ([ed3c038](https://github.com/feasibleone/blong/commit/ed3c038352644d2d8f828b3cb69cbea718e7084b))
|
|
16
|
+
* object schema ([688d5d2](https://github.com/feasibleone/blong/commit/688d5d2a4a895c8feba7b898da03c46b3a3f3292))
|
|
17
|
+
* playwright coverage ([616cf61](https://github.com/feasibleone/blong/commit/616cf61f8616dc7fa6fac8f75fcd5a8577e70448))
|
|
18
|
+
* schema helpers and constraints ([8409257](https://github.com/feasibleone/blong/commit/84092571f6b327c1166ef142f70cb7d91b013eaf))
|
|
19
|
+
* schema sync ([ae63684](https://github.com/feasibleone/blong/commit/ae63684ea85d2b0f7adc8e8a380e51e0564d54c4))
|
|
20
|
+
* seed ([5c76603](https://github.com/feasibleone/blong/commit/5c7660392bc1197638810e864bfd47ce1218b2e5))
|
|
21
|
+
* support expressions in the configuration ([843bb58](https://github.com/feasibleone/blong/commit/843bb58c97456d85eac573874c90755bdb68bc95))
|
|
22
|
+
* vitest coverage ([4c6d9cb](https://github.com/feasibleone/blong/commit/4c6d9cb941957b0291cbe091e06884589b1e8f06))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
### Bug Fixes
|
|
26
|
+
|
|
27
|
+
* authorization flow test ([f816dc2](https://github.com/feasibleone/blong/commit/f816dc2fb0d1556dff04d42901cfe5c36cca0717))
|
|
28
|
+
* build ([de22713](https://github.com/feasibleone/blong/commit/de2271393350945b8d793ca0ec89670465bba085))
|
|
29
|
+
* core.resource merging ([f191055](https://github.com/feasibleone/blong/commit/f1910558ce0f369f556911c4c0f6397b8003fd33))
|
|
30
|
+
* full stack alignment for CRUD ([8795e08](https://github.com/feasibleone/blong/commit/8795e087182402ac28c3ea03c6bee27319069625))
|
|
31
|
+
* handle existing PK ([fb0041b](https://github.com/feasibleone/blong/commit/fb0041b8f6ac7ba2f0dc0a752ab19dd49c09d79f))
|
|
32
|
+
* hanging playwright install ([13bf7a4](https://github.com/feasibleone/blong/commit/13bf7a4031f1f94160e0cb9d8b4f83a1732e70af))
|
|
33
|
+
* keep running the server in CI for the playwright intent ([5d01f3b](https://github.com/feasibleone/blong/commit/5d01f3b044f4506c2f6fab3c2c048b116b59accb))
|
|
34
|
+
* login ([4def73d](https://github.com/feasibleone/blong/commit/4def73d33473d2b9183e91fa1ab030b2ccb85ad5))
|
|
35
|
+
* Playwright, Storybook and realm DRY ([4302512](https://github.com/feasibleone/blong/commit/43025123e613ef43e0ac5b83ab00fbc0cabfccf2))
|
|
36
|
+
* Playwright, Storybook and realm DRY ([7bbfad8](https://github.com/feasibleone/blong/commit/7bbfad8160da0ead5600ad56b543e935bfd01954))
|
|
37
|
+
* portal loading in the browser ([5c2bc76](https://github.com/feasibleone/blong/commit/5c2bc762dea53d7d2caa0022ea3d7e3f106fdddb))
|
|
38
|
+
|
|
39
|
+
## [1.25.1](https://github.com/feasibleone/blong/compare/blong-gogo-v1.25.0...blong-gogo-v1.25.1) (2026-05-25)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
### Bug Fixes
|
|
43
|
+
|
|
44
|
+
* dependencies ([cc6ba83](https://github.com/feasibleone/blong/commit/cc6ba830a721dfb063f5331d44e26a97e28c7f9a))
|
|
45
|
+
* improve testing ([2b1d862](https://github.com/feasibleone/blong/commit/2b1d862fda71ed7910bc99f4bbbe11ef17be098c))
|
|
46
|
+
|
|
3
47
|
## [1.25.0](https://github.com/feasibleone/blong/compare/blong-gogo-v1.24.0...blong-gogo-v1.25.0) (2026-05-19)
|
|
4
48
|
|
|
5
49
|
|
package/bin/blong-watch.ts
CHANGED
|
@@ -9,7 +9,7 @@ const argv: {_: string[]} = minimist(process.argv.slice(2));
|
|
|
9
9
|
|
|
10
10
|
// The first positional arg is an optional file/folder target; the rest are intents.
|
|
11
11
|
const [maybeTarget, ...rest] = argv._;
|
|
12
|
-
const target = maybeTarget && existsSync(resolve(maybeTarget)) ? maybeTarget : undefined;
|
|
12
|
+
const target = maybeTarget && existsSync(resolve(maybeTarget)) ? resolve(maybeTarget) : undefined;
|
|
13
13
|
const intents = target ? rest : argv._;
|
|
14
14
|
|
|
15
15
|
await autoRun({cwd: process.cwd(), target, intents});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@feasibleone/blong-gogo",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.26.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"url": "git+https://github.com/feasibleone/blong.git"
|
|
6
6
|
},
|
|
@@ -24,13 +24,17 @@
|
|
|
24
24
|
"@fastify/bearer-auth": "^10.1.2",
|
|
25
25
|
"@fastify/cookie": "^11.0.2",
|
|
26
26
|
"@fastify/cors": "^11.2.0",
|
|
27
|
+
"@fastify/deepmerge": "^3.2.1",
|
|
27
28
|
"@fastify/swagger": "^9.7.0",
|
|
28
29
|
"@fastify/swagger-ui": "^5.2.5",
|
|
30
|
+
"@fastify/type-provider-typebox": "^6.1.0",
|
|
29
31
|
"@feasibleone/blong": "^1.0.0",
|
|
30
32
|
"@feasibleone/blong-chain": "^1.0.0",
|
|
31
33
|
"@feasibleone/blong-config": "^1.0.0",
|
|
32
34
|
"@feasibleone/blong-kopi": "^1.0.0",
|
|
33
|
-
"@
|
|
35
|
+
"@feasibleone/blong-lib": "^1.0.0",
|
|
36
|
+
"@feasibleone/blong-mock": "^1.0.0",
|
|
37
|
+
"@keycloak/keycloak-admin-client": "^26.6.2",
|
|
34
38
|
"@kubernetes/client-node": "^1.4.0",
|
|
35
39
|
"@octokit/rest": "^22.0.1",
|
|
36
40
|
"@sinclair/typebox-codegen": "^0.11.1",
|
|
@@ -43,7 +47,7 @@
|
|
|
43
47
|
"fastify-plugin": "^5.1.0",
|
|
44
48
|
"got": "^14.6.6",
|
|
45
49
|
"jose": "^6.2.1",
|
|
46
|
-
"knex": "^3.
|
|
50
|
+
"knex": "^3.2.10",
|
|
47
51
|
"ky": "^1.14.2",
|
|
48
52
|
"lru-cache": "^11.2.4",
|
|
49
53
|
"minimist": "^1.2.8",
|
|
@@ -64,10 +68,12 @@
|
|
|
64
68
|
"undici": "^8.0.1",
|
|
65
69
|
"ut-bitsyntax": "^6.2.7",
|
|
66
70
|
"ut-dns-discovery": "^6.2.3",
|
|
71
|
+
"ut-function.cbc": "^1.1.6",
|
|
67
72
|
"ut-function.interpolate": "1.1.3",
|
|
68
73
|
"ut-function.merge": "^1.5.6",
|
|
69
74
|
"uuid": "^13.0.0",
|
|
70
|
-
"yaml": "^2.8.3"
|
|
75
|
+
"yaml": "^2.8.3",
|
|
76
|
+
"@feasibleone/blong-template": "1.0.0"
|
|
71
77
|
},
|
|
72
78
|
"devDependencies": {
|
|
73
79
|
"@rushstack/heft": "^1.2.6",
|
|
@@ -75,17 +81,17 @@
|
|
|
75
81
|
"@rushstack/heft-typescript-plugin": "^1.3.1",
|
|
76
82
|
"@types/cacache": "^20.0.1",
|
|
77
83
|
"@types/node": "^24",
|
|
78
|
-
"playwright": "^1.
|
|
84
|
+
"playwright": "^1.60.0",
|
|
79
85
|
"tap": "^21.6.3",
|
|
80
|
-
"typescript": "^
|
|
81
|
-
"@feasibleone/blong-dev": "1.
|
|
86
|
+
"typescript": "^6.0.3",
|
|
87
|
+
"@feasibleone/blong-dev": "1.1.0"
|
|
82
88
|
},
|
|
83
89
|
"scripts": {
|
|
84
90
|
"browser-check": "node scripts/browser-compat-check.mjs",
|
|
85
91
|
"build": "true",
|
|
86
|
-
"ci-coverage": "
|
|
92
|
+
"ci-coverage": "./run-coverage.sh",
|
|
87
93
|
"ci-lint": "blong-dev lint",
|
|
88
94
|
"ci-publish": "node ../../common/scripts/install-run-rush-pnpm.js publish --access public --provenance",
|
|
89
|
-
"ci-
|
|
95
|
+
"ci-test": "blong-dev test"
|
|
90
96
|
}
|
|
91
97
|
}
|
package/src/AdapterBase.ts
CHANGED
|
@@ -11,7 +11,6 @@ import type {
|
|
|
11
11
|
} from '@feasibleone/blong/types';
|
|
12
12
|
import type net from 'node:net';
|
|
13
13
|
import PQueue from 'p-queue';
|
|
14
|
-
import merge from 'ut-function.merge';
|
|
15
14
|
|
|
16
15
|
import ConfigRuntime from './ConfigRuntime.ts';
|
|
17
16
|
import {isExpectedError} from './lib.ts';
|
|
@@ -87,6 +86,7 @@ export class AdapterBase<T, C extends IContext> implements AdapterHandlerContext
|
|
|
87
86
|
configBase: string;
|
|
88
87
|
log: unknown = null;
|
|
89
88
|
importedMap?: Map<string, Record<string, (...args: unknown[]) => unknown>>;
|
|
89
|
+
platform: IApi['platform'];
|
|
90
90
|
|
|
91
91
|
// These are prefixed with _ rather than using # private class fields.
|
|
92
92
|
// The adapter uses Object.setPrototypeOf(current, base) to set the base as
|
|
@@ -103,7 +103,7 @@ export class AdapterBase<T, C extends IContext> implements AdapterHandlerContext
|
|
|
103
103
|
// api.attachHandlers = ... only *after* the port factory returns. Storing
|
|
104
104
|
// the api object and reading api.attachHandlers lazily (at call time)
|
|
105
105
|
// ensures we always see the real function rather than the initial undefined.
|
|
106
|
-
_api: Pick<IApi, 'attachHandlers'>;
|
|
106
|
+
_api: Pick<IApi, 'attachHandlers' | 'render' | 'attach'>;
|
|
107
107
|
_createLog: IApi['createLog'];
|
|
108
108
|
_attachCheckpoint: IApi['attachCheckpoint'];
|
|
109
109
|
_activationNames: string[];
|
|
@@ -123,6 +123,9 @@ export class AdapterBase<T, C extends IContext> implements AdapterHandlerContext
|
|
|
123
123
|
| 'attachHandlers'
|
|
124
124
|
| 'createLog'
|
|
125
125
|
| 'attachCheckpoint'
|
|
126
|
+
| 'render'
|
|
127
|
+
| 'platform'
|
|
128
|
+
| 'attach'
|
|
126
129
|
>,
|
|
127
130
|
configBase: string,
|
|
128
131
|
activationNames: string[] = [],
|
|
@@ -133,6 +136,7 @@ export class AdapterBase<T, C extends IContext> implements AdapterHandlerContext
|
|
|
133
136
|
this._dispatch = api.dispatch;
|
|
134
137
|
this._methodId = api.methodId;
|
|
135
138
|
this._getPath = api.getPath;
|
|
139
|
+
this.platform = api.platform;
|
|
136
140
|
this._api = api;
|
|
137
141
|
this._createLog = api.createLog;
|
|
138
142
|
this._attachCheckpoint = api.attachCheckpoint;
|
|
@@ -150,7 +154,7 @@ export class AdapterBase<T, C extends IContext> implements AdapterHandlerContext
|
|
|
150
154
|
}
|
|
151
155
|
|
|
152
156
|
async init(...configs: object[]): Promise<void> {
|
|
153
|
-
this.config =
|
|
157
|
+
this.config = this._api.render([this.activeConfig(), ...configs]) as Config<T, C>;
|
|
154
158
|
this.log = this._createLog?.(this.config.logLevel || 'info', {
|
|
155
159
|
...this.config.log,
|
|
156
160
|
name: this.config.id,
|
|
@@ -275,7 +279,7 @@ export class AdapterBase<T, C extends IContext> implements AdapterHandlerContext
|
|
|
275
279
|
async drain(): Promise<void> {}
|
|
276
280
|
|
|
277
281
|
findHandler(methodName: string): unknown {
|
|
278
|
-
methodName = this._methodId(methodName);
|
|
282
|
+
methodName = this.methodPath(this._methodId(methodName));
|
|
279
283
|
return this.imported[methodName];
|
|
280
284
|
}
|
|
281
285
|
|
|
@@ -323,12 +327,12 @@ export class AdapterBase<T, C extends IContext> implements AdapterHandlerContext
|
|
|
323
327
|
return this.event('start', {configBase: this.configBase, config});
|
|
324
328
|
}
|
|
325
329
|
|
|
326
|
-
async
|
|
330
|
+
async attach(
|
|
327
331
|
patterns: (string | RegExp)[] | string | RegExp,
|
|
328
|
-
target:
|
|
332
|
+
target: object = {},
|
|
329
333
|
): Promise<object> {
|
|
330
|
-
await this._api.
|
|
331
|
-
return target
|
|
334
|
+
await this._api.attach(target, patterns);
|
|
335
|
+
return target;
|
|
332
336
|
}
|
|
333
337
|
|
|
334
338
|
async handle(...params: unknown[]): Promise<unknown> {
|
|
@@ -371,17 +375,27 @@ export default async function adapter<T, C extends IContext>(
|
|
|
371
375
|
configBase: string,
|
|
372
376
|
activationNames: string[] = [],
|
|
373
377
|
): Promise<Adapter<T, C>> {
|
|
374
|
-
const {
|
|
378
|
+
const {
|
|
379
|
+
adapter: adapterFactory,
|
|
380
|
+
utError,
|
|
381
|
+
handlers,
|
|
382
|
+
remote,
|
|
383
|
+
rpc,
|
|
384
|
+
local,
|
|
385
|
+
registry,
|
|
386
|
+
type,
|
|
387
|
+
schema,
|
|
388
|
+
} = api;
|
|
375
389
|
_errors ||= utError.register(errorMap);
|
|
376
390
|
|
|
377
391
|
const base = new AdapterBase<T, C>(api, configBase, activationNames);
|
|
378
392
|
|
|
379
|
-
const result = handlers!({utError, remote, type});
|
|
393
|
+
const result = handlers!({utError, remote, type, schema: registry.objectSchema, manifest: api.manifest});
|
|
380
394
|
let current = result;
|
|
381
395
|
while (current.extends) {
|
|
382
396
|
const parent = await (typeof current.extends === 'string'
|
|
383
|
-
? adapterFactory(current.extends)!({utError, remote, rpc, local, registry})
|
|
384
|
-
: current.extends({utError, remote, rpc, local, registry}));
|
|
397
|
+
? adapterFactory(current.extends)!({utError, remote, rpc, local, registry, schema, manifest: api.manifest})
|
|
398
|
+
: current.extends({utError, remote, rpc, local, registry, schema, manifest: api.manifest}));
|
|
385
399
|
Object.setPrototypeOf(current, parent);
|
|
386
400
|
current = parent;
|
|
387
401
|
}
|
package/src/ConfigRuntime.ts
CHANGED
|
@@ -133,7 +133,12 @@ export function createConfigProxy<T extends object>(
|
|
|
133
133
|
get(_target, prop) {
|
|
134
134
|
const container = getNode(path);
|
|
135
135
|
const val = container == null ? undefined : Reflect.get(container as object, prop);
|
|
136
|
-
if (
|
|
136
|
+
if (
|
|
137
|
+
val === undefined ||
|
|
138
|
+
val === null ||
|
|
139
|
+
typeof val !== 'object' ||
|
|
140
|
+
val.constructor !== Object
|
|
141
|
+
) {
|
|
137
142
|
if (
|
|
138
143
|
_factoryPhase.active &&
|
|
139
144
|
val !== undefined &&
|
|
@@ -252,7 +257,9 @@ export function deepDiff(prev: unknown, next: unknown, path = ''): ConfigDiff {
|
|
|
252
257
|
// ---------------------------------------------------------------------------
|
|
253
258
|
|
|
254
259
|
export default class ConfigRuntime implements IConfigRuntime {
|
|
255
|
-
readonly #
|
|
260
|
+
readonly #baseConfig: object;
|
|
261
|
+
readonly #configs: object[];
|
|
262
|
+
readonly #suite: string;
|
|
256
263
|
readonly #subscribers: Set<ConfigSubscriber> = new Set();
|
|
257
264
|
|
|
258
265
|
#rawSnapshot: object = {};
|
|
@@ -262,8 +269,10 @@ export default class ConfigRuntime implements IConfigRuntime {
|
|
|
262
269
|
readonly enterConfig: (mode?: FactoryPhaseMode) => void;
|
|
263
270
|
readonly exitConfig: () => Error[];
|
|
264
271
|
|
|
265
|
-
public constructor(
|
|
266
|
-
this.#
|
|
272
|
+
public constructor(baseConfig: object, configs: (object | boolean)[], suite: string) {
|
|
273
|
+
this.#baseConfig = baseConfig;
|
|
274
|
+
this.#configs = configs.filter(Boolean) as object[];
|
|
275
|
+
this.#suite = suite;
|
|
267
276
|
const {proxy, update, enterConfig, exitConfig} = createConfigProxy(this.#rawSnapshot);
|
|
268
277
|
this.#proxy = proxy;
|
|
269
278
|
this.#updateProxy = update;
|
|
@@ -283,13 +292,18 @@ export default class ConfigRuntime implements IConfigRuntime {
|
|
|
283
292
|
return this.#rawSnapshot;
|
|
284
293
|
}
|
|
285
294
|
|
|
295
|
+
private mergeConfigs(blongConfig: object): object {
|
|
296
|
+
const loaded = loadBlong(blongConfig);
|
|
297
|
+
return merge({}, this.#baseConfig, ...this.#configs, loaded);
|
|
298
|
+
}
|
|
299
|
+
|
|
286
300
|
/**
|
|
287
301
|
* Load configuration from all sources using blong-config.
|
|
288
302
|
* Merges the result into the proxy backing store so existing references
|
|
289
303
|
* automatically reflect the new values.
|
|
290
304
|
*/
|
|
291
305
|
public async load(params: object = {}): Promise<object> {
|
|
292
|
-
const loaded =
|
|
306
|
+
const loaded = this.mergeConfigs({config: {suite: this.#suite}, ...params});
|
|
293
307
|
this.#rawSnapshot = loaded;
|
|
294
308
|
this.#updateProxy(loaded);
|
|
295
309
|
return this.#proxy;
|
|
@@ -304,7 +318,7 @@ export default class ConfigRuntime implements IConfigRuntime {
|
|
|
304
318
|
*/
|
|
305
319
|
public async reload(): Promise<ConfigDiff> {
|
|
306
320
|
const prev = this.#rawSnapshot;
|
|
307
|
-
const next =
|
|
321
|
+
const next = this.mergeConfigs({config: {suite: this.#suite}});
|
|
308
322
|
const diff = this.diff(prev, next);
|
|
309
323
|
|
|
310
324
|
// Only do work when something actually changed
|
|
@@ -390,17 +404,4 @@ export default class ConfigRuntime implements IConfigRuntime {
|
|
|
390
404
|
.flat(),
|
|
391
405
|
]);
|
|
392
406
|
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* Merge module configs from multiple sources in the standard order.
|
|
396
|
-
*
|
|
397
|
-
* Previously done in `loadRealm()`:
|
|
398
|
-
* `merge(mergedConfig, ...loadedConfigs.filter(Boolean))`
|
|
399
|
-
*
|
|
400
|
-
* @param base - the base config object (mutated in place)
|
|
401
|
-
* @param sources - config objects to merge, in priority order
|
|
402
|
-
*/
|
|
403
|
-
static mergeModuleConfig(base: object, ...sources: unknown[]): object {
|
|
404
|
-
return merge(base, ...sources.filter(Boolean));
|
|
405
|
-
}
|
|
406
407
|
}
|
package/src/Gateway.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
IGateway,
|
|
8
8
|
ILocal,
|
|
9
9
|
ILog,
|
|
10
|
+
IManifest,
|
|
10
11
|
IMeta,
|
|
11
12
|
IPlatformApi,
|
|
12
13
|
} from '@feasibleone/blong/types';
|
|
@@ -15,12 +16,14 @@ import fastify, {type FastifyReply, type FastifyRequest, type RouteOptions} from
|
|
|
15
16
|
import os from 'os';
|
|
16
17
|
import type {LevelWithSilent} from 'pino';
|
|
17
18
|
import {Type, type TSchema} from 'typebox';
|
|
19
|
+
import Value from 'typebox/value';
|
|
18
20
|
import {v4} from 'uuid';
|
|
19
21
|
|
|
22
|
+
import {type TypeBoxTypeProvider} from '@fastify/type-provider-typebox';
|
|
20
23
|
import type {IResolution} from './Resolution.ts';
|
|
21
24
|
import type {IRpcClient} from './RpcClient.ts';
|
|
22
25
|
import jwt from './jwt.ts';
|
|
23
|
-
import {isExpectedError, methodParts, snakeToCamel} from './lib.ts';
|
|
26
|
+
import {isExpectedError, methodId, methodParts, snakeToCamel} from './lib.ts';
|
|
24
27
|
import type {IConfig as IConfigMLE} from './mle.ts';
|
|
25
28
|
import swagger from './swagger.ts';
|
|
26
29
|
|
|
@@ -68,6 +71,13 @@ interface IConfig extends IConfigMLE {
|
|
|
68
71
|
* production to prevent callers from suppressing audit-level error logs.
|
|
69
72
|
*/
|
|
70
73
|
expectedErrors?: boolean;
|
|
74
|
+
/**
|
|
75
|
+
* Optional handler name (semantic triple) that returns the list of allowed
|
|
76
|
+
* actions for the authenticated user. When set, the gateway calls this
|
|
77
|
+
* handler during authentication and stores the result in credentials,
|
|
78
|
+
* then enforces that every request's methodId is in the allowed list.
|
|
79
|
+
*/
|
|
80
|
+
authorize?: string;
|
|
71
81
|
errorFields: unknown[];
|
|
72
82
|
jwt: {
|
|
73
83
|
cache: object;
|
|
@@ -146,7 +156,7 @@ export default class Gateway extends Internal implements IGateway {
|
|
|
146
156
|
#config: IConfig = {
|
|
147
157
|
host: '0.0.0.0',
|
|
148
158
|
port: 8080,
|
|
149
|
-
logLevel: '
|
|
159
|
+
logLevel: 'warn',
|
|
150
160
|
cors: undefined,
|
|
151
161
|
sign: undefined as IConfig['sign'] | undefined,
|
|
152
162
|
encrypt: undefined as IConfig['encrypt'] | undefined,
|
|
@@ -156,6 +166,7 @@ export default class Gateway extends Internal implements IGateway {
|
|
|
156
166
|
},
|
|
157
167
|
debug: false,
|
|
158
168
|
expectedErrors: false,
|
|
169
|
+
authorize: undefined,
|
|
159
170
|
errorFields: [],
|
|
160
171
|
jwt: {
|
|
161
172
|
cache: {},
|
|
@@ -170,6 +181,7 @@ export default class Gateway extends Internal implements IGateway {
|
|
|
170
181
|
#errorFields: [string, unknown][] = [];
|
|
171
182
|
#plugins: {plugin: unknown; options: unknown}[] = [];
|
|
172
183
|
#platform: IPlatformApi;
|
|
184
|
+
#manifest: IManifest | undefined;
|
|
173
185
|
|
|
174
186
|
public constructor(
|
|
175
187
|
config: IConfig,
|
|
@@ -180,6 +192,7 @@ export default class Gateway extends Internal implements IGateway {
|
|
|
180
192
|
resolution,
|
|
181
193
|
local,
|
|
182
194
|
platform,
|
|
195
|
+
manifest,
|
|
183
196
|
}: {
|
|
184
197
|
log?: ILog;
|
|
185
198
|
error?: IErrorFactory;
|
|
@@ -187,6 +200,7 @@ export default class Gateway extends Internal implements IGateway {
|
|
|
187
200
|
local?: ILocal;
|
|
188
201
|
resolution?: IResolution;
|
|
189
202
|
platform?: IPlatformApi;
|
|
203
|
+
manifest?: IManifest;
|
|
190
204
|
},
|
|
191
205
|
) {
|
|
192
206
|
super({log});
|
|
@@ -194,6 +208,16 @@ export default class Gateway extends Internal implements IGateway {
|
|
|
194
208
|
this.#platform = platform!;
|
|
195
209
|
|
|
196
210
|
this.merge(this.#config, config);
|
|
211
|
+
this.#config.sign ||= (
|
|
212
|
+
process.env.GATEWAY_SIGN_KEY
|
|
213
|
+
? {env: 'GATEWAY_SIGN_KEY'}
|
|
214
|
+
: {generate: {alg: 'ES384', crv: 'P-384', use: 'sig'}}
|
|
215
|
+
) as IConfig['sign'];
|
|
216
|
+
this.#config.encrypt ||= (
|
|
217
|
+
process.env.GATEWAY_ENCRYPT_KEY
|
|
218
|
+
? {env: 'GATEWAY_ENCRYPT_KEY'}
|
|
219
|
+
: {generate: {alg: 'ECDH-ES+A256KW', crv: 'P-384', use: 'enc'}}
|
|
220
|
+
) as IConfig['encrypt'];
|
|
197
221
|
this.#errorFields = Object.entries({
|
|
198
222
|
type: true,
|
|
199
223
|
message: true,
|
|
@@ -210,6 +234,7 @@ export default class Gateway extends Internal implements IGateway {
|
|
|
210
234
|
this.#resolution = resolution!;
|
|
211
235
|
this.#rpcClient = remote!;
|
|
212
236
|
this.#local = local!;
|
|
237
|
+
this.#manifest = manifest;
|
|
213
238
|
}
|
|
214
239
|
|
|
215
240
|
protected config(): object {
|
|
@@ -336,6 +361,7 @@ export default class Gateway extends Internal implements IGateway {
|
|
|
336
361
|
: `/${value.basePath ?? 'rpc'}/${method.split('.').join('/')}`,
|
|
337
362
|
config: {
|
|
338
363
|
auth: value.auth ?? 'jwt',
|
|
364
|
+
methodName: method,
|
|
339
365
|
},
|
|
340
366
|
schema: Type && {
|
|
341
367
|
...('body' in value
|
|
@@ -539,11 +565,34 @@ export default class Gateway extends Internal implements IGateway {
|
|
|
539
565
|
strict: false,
|
|
540
566
|
},
|
|
541
567
|
},
|
|
568
|
+
}).withTypeProvider<TypeBoxTypeProvider>();
|
|
569
|
+
|
|
570
|
+
this.#server.setValidatorCompiler(({schema}: {schema: TSchema}) => {
|
|
571
|
+
return (data: unknown) => {
|
|
572
|
+
// 1. Runtime validation check
|
|
573
|
+
if (!Value.Check(schema, data)) {
|
|
574
|
+
const errors = [...Value.Errors(schema, data)];
|
|
575
|
+
return {
|
|
576
|
+
error: new Error(
|
|
577
|
+
`Validation failed: ${errors.map(e => e.message).join(', ')}`,
|
|
578
|
+
),
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
// 2. Transform/Decode the validated string into a Date object
|
|
583
|
+
try {
|
|
584
|
+
const decoded = Value.Decode(schema, data);
|
|
585
|
+
return {value: decoded};
|
|
586
|
+
} catch (error) {
|
|
587
|
+
return {error};
|
|
588
|
+
}
|
|
589
|
+
};
|
|
542
590
|
});
|
|
591
|
+
|
|
543
592
|
this.#server.setErrorHandler(
|
|
544
593
|
(error: Record<string, unknown>, request: FastifyRequest, reply: FastifyReply) => {
|
|
545
594
|
request.log.error({err: error}, 'gateway unhandled error');
|
|
546
|
-
return reply.status(500).send({
|
|
595
|
+
return reply.status((error as {statusCode?: number}).statusCode || 500).send({
|
|
547
596
|
jsonrpc: '2.0',
|
|
548
597
|
id: (request.body as {id?: unknown})?.id,
|
|
549
598
|
error: this._formatError(error),
|
|
@@ -556,6 +605,10 @@ export default class Gateway extends Internal implements IGateway {
|
|
|
556
605
|
this.#rpcClient.verify(token, options),
|
|
557
606
|
errors: this.#errors,
|
|
558
607
|
audience: this.#config.jwt.audience,
|
|
608
|
+
authorize: this.#config.authorize,
|
|
609
|
+
local: this.#local,
|
|
610
|
+
methodId: methodId,
|
|
611
|
+
methodParts: methodParts,
|
|
559
612
|
});
|
|
560
613
|
if (this.#config.cors)
|
|
561
614
|
await this.#server.register((await import('./cors.ts')).default, this.#config.cors);
|
|
@@ -584,6 +637,13 @@ export default class Gateway extends Internal implements IGateway {
|
|
|
584
637
|
port: this.#config.port,
|
|
585
638
|
host: this.#config.host,
|
|
586
639
|
});
|
|
640
|
+
// Publish the effective port to the manifest so other platforms
|
|
641
|
+
// (e.g. browser backend adapter) can discover it.
|
|
642
|
+
// The manifest proxy automatically resolves any pending deferred.
|
|
643
|
+
const address = this.#server.server.address();
|
|
644
|
+
if (this.#manifest && address && typeof address === 'object') {
|
|
645
|
+
this.#manifest.gatewayPort = address.port;
|
|
646
|
+
}
|
|
587
647
|
return this;
|
|
588
648
|
}
|
|
589
649
|
|