cabloy 5.1.218 → 5.1.220

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.cabloy-version +1 -1
  2. package/.github/workflows/create-cabloy.yml +1 -1
  3. package/CHANGELOG.md +19 -0
  4. package/README.md +1 -1
  5. package/package.json +1 -1
  6. package/repo-docs/fullstack/quickstart.md +3 -3
  7. package/repo-docs/reference/repo-scripts.md +12 -0
  8. package/scripts/generateOpenapiIsolated.test.ts +25 -0
  9. package/scripts/generateOpenapiIsolated.ts +28 -11
  10. package/scripts/runIsolatedTest.test.ts +157 -45
  11. package/scripts/runIsolatedTest.ts +30 -6
  12. package/scripts/strictGenerationStartup.test.ts +212 -0
  13. package/vona/packages-cli/cli/package.json +1 -1
  14. package/vona/packages-cli/cli-set-api/package.json +1 -1
  15. package/vona/packages-vona/vona/package.json +1 -1
  16. package/vona/packages-vona/vona-core/package.json +1 -1
  17. package/vona/packages-vona/vona-core/src/lib/bean/beanBase.ts +4 -0
  18. package/vona/packages-vona/vona-core/src/lib/core/asyncLocalStorage.ts +84 -12
  19. package/vona/packages-vona/vona-core/src/lib/core/index.ts +14 -0
  20. package/vona/packages-vona/vona-core/src/lib/core/logger/appLogger.ts +11 -3
  21. package/vona/packages-vona/vona-core/src/lib/core/logger/diagnosticLogger.test.ts +146 -0
  22. package/vona/packages-vona/vona-core/src/lib/core/logger/diagnosticLogger.ts +138 -0
  23. package/vona/packages-vona/vona-core/src/lib/core/logger/index.ts +1 -0
  24. package/vona/packages-vona/vona-core/src/lib/core/logger/utils.ts +25 -5
  25. package/vona/packages-vona/vona-core/src/lib/core/requestBoundary.ts +89 -0
  26. package/vona/packages-vona/vona-core/src/lib/core/testFreshness.test.ts +379 -0
  27. package/vona/packages-vona/vona-core/src/lib/core/testFreshness.ts +238 -0
  28. package/vona/packages-vona/vona-core/src/lib/framework/start.test.ts +4 -3
  29. package/vona/packages-vona/vona-core/src/lib/framework/start.ts +2 -0
  30. package/vona/packages-vona/vona-mock/package.json +1 -1
  31. package/vona/pnpm-lock.yaml +218 -43
  32. package/vona/pnpm-workspace.yaml +1 -1
  33. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  34. package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
  35. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/bean/bean.executor.ts +111 -60
  36. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/service/executor.ts +6 -6
  37. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/types/executor.ts +13 -1
  38. package/vona/src/suite-vendor/a-vona/modules/a-jwt/package.json +1 -1
  39. package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/lib/jwtClientLogging.test.ts +182 -0
  40. package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/service/jwtClient.ts +2 -8
  41. package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
  42. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/aopMethod.log.ts +4 -1
  43. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/bean.logger.ts +9 -0
  44. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/middlewareSystem.httpLog.ts +27 -14
  45. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactHttpLog.test.ts +207 -0
  46. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactHttpLog.ts +30 -0
  47. package/vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts +33 -1
  48. package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
  49. package/vona/src/suite-vendor/a-vona/modules/a-startup/src/service/startup.ts +6 -0
  50. package/vona/src/suite-vendor/a-vona/modules/a-startup/test/devSeed.test.ts +2 -2
  51. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/package.json +1 -1
  52. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/bean.telemetry.ts +8 -1
  53. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/middlewareSystem.trace.ts +9 -14
  54. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/lib/spanDiagnostics.ts +70 -0
  55. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/service/telemetry.ts +37 -23
  56. package/vona/src/suite-vendor/a-vona/modules/a-version/package.json +1 -1
  57. package/vona/src/suite-vendor/a-vona/modules/a-version/src/service/database.ts +31 -4
  58. package/vona/src/suite-vendor/a-vona/package.json +2 -2
  59. package/zova/packages-cli/cli/package.json +2 -2
  60. package/zova/packages-cli/cli-set-front/package.json +1 -1
  61. package/zova/packages-cli/cli-set-front/src/lib/bean/cli.bin.buildRest.ts +11 -101
  62. package/zova/packages-cli/cli-set-front/src/lib/common/restDependencies.ts +78 -0
  63. package/zova/packages-cli/cli-set-front/src/lib/common/restDependencyVersions.ts +231 -0
  64. package/zova/packages-cli/cli-set-front/test/restDependencies.test.ts +431 -0
  65. package/zova/pnpm-lock.yaml +8 -8
  66. /package/vona/patches/{zova-core@5.1.92.patch → zova-core@5.1.93.patch} +0 -0
@@ -1,12 +1,12 @@
1
- import type { FunctionAsync } from 'vona';
1
+ import type { FunctionAsync, VonaContext } from 'vona';
2
2
  import type { IApiPathRecordMethodMap } from 'vona-module-a-web';
3
3
 
4
4
  import { isNil } from '@cabloy/utils';
5
- import { BeanBase } from 'vona';
5
+ import { BeanBase, captureRequestBoundary } from 'vona';
6
6
  import { Bean } from 'vona-module-a-bean';
7
7
  import { ServiceTransactionAsyncLocalStorage } from 'vona-module-a-orm';
8
8
 
9
- import type { INewCtxOptions, IPerformActionOptions } from '../types/executor.ts';
9
+ import type { INewCtxLifecycle, INewCtxOptions, IPerformActionOptions } from '../types/executor.ts';
10
10
 
11
11
  import { __delegateProperties } from '../lib/utils.ts';
12
12
 
@@ -30,6 +30,7 @@ export class BeanExecutor extends BeanBase {
30
30
  async newCtxIsolate<RESULT>(
31
31
  fn: FunctionAsync<RESULT>,
32
32
  options?: INewCtxOptions,
33
+ lifecycle?: INewCtxLifecycle,
33
34
  ): Promise<RESULT> {
34
35
  options = Object.assign({}, options);
35
36
  // instanceName !== undefined means isolate
@@ -39,83 +40,133 @@ export class BeanExecutor extends BeanBase {
39
40
  options.locale = options.locale === undefined ? this.ctx?.locale : options.locale;
40
41
  options.tz = options.tz === undefined ? this.ctx?.tz : options.tz;
41
42
  }
42
- return this.newCtx(fn, options);
43
+ return this.newCtx(fn, options, lifecycle);
43
44
  }
44
45
 
45
- async mockCtx<RESULT>(fn: FunctionAsync<RESULT>, options?: INewCtxOptions): Promise<RESULT> {
46
+ async mockCtx<RESULT>(
47
+ fn: FunctionAsync<RESULT>,
48
+ options?: INewCtxOptions,
49
+ lifecycle?: INewCtxLifecycle,
50
+ ): Promise<RESULT> {
46
51
  options = Object.assign({}, options);
47
52
  options.dbInfo = isNil(options.dbInfo) ? { level: 1 } : options.dbInfo;
48
53
  options.instanceName =
49
54
  options.instanceName === undefined ? (this.ctx?.instanceName ?? '') : options.instanceName;
50
55
  options.instance = !isNil(options.instanceName);
51
- return this.newCtx(fn, options);
56
+ return this.newCtx(fn, options, lifecycle);
52
57
  }
53
58
 
54
- async newCtx<RESULT>(fn: FunctionAsync<RESULT>, options?: INewCtxOptions): Promise<RESULT> {
55
- return this._newCtxInnerStep1(() => {
56
- if (isNil(options?.dbInfo)) return this._newCtxInnerStep2(fn, options);
57
- return this.bean.database.switchDb(() => {
58
- return this._newCtxInnerStep2(fn, options);
59
- }, options?.dbInfo);
60
- }, options);
59
+ async newCtx<RESULT>(
60
+ fn: FunctionAsync<RESULT>,
61
+ options?: INewCtxOptions,
62
+ lifecycle?: INewCtxLifecycle,
63
+ ): Promise<RESULT> {
64
+ return this._newCtxInnerStep1(
65
+ () => {
66
+ if (isNil(options?.dbInfo)) return this._newCtxInnerStep2(fn, options);
67
+ return this.bean.database.switchDb(() => {
68
+ return this._newCtxInnerStep2(fn, options);
69
+ }, options?.dbInfo);
70
+ },
71
+ options,
72
+ lifecycle,
73
+ );
61
74
  }
62
75
 
63
76
  private async _newCtxInnerStep1<RESULT>(
64
77
  fn: FunctionAsync<RESULT>,
65
- options?: INewCtxOptions,
78
+ options: INewCtxOptions = {},
79
+ lifecycle?: INewCtxLifecycle,
66
80
  ): Promise<RESULT> {
67
- options = Object.assign({}, options);
68
- // innerAccess
69
- const innerAccess = options.innerAccess !== false;
70
- // isolate
71
- const isolate =
72
- !this.ctx ||
73
- options.instanceName !== undefined ||
74
- (!innerAccess && this.ctx?.instanceName === null);
75
- const ctxCaller = !isolate && this.ctx ? this.ctx : undefined;
76
- // locale/tz/instanceName
77
- if (!isolate && this.ctx) {
78
- options.locale = options.locale === undefined ? this.ctx.locale : options.locale;
79
- options.tz = options.tz === undefined ? this.ctx.tz : options.tz;
80
- options.instanceName =
81
- options.instanceName === undefined
82
- ? !innerAccess && this.ctx.instanceName === null
83
- ? undefined
84
- : this.ctx.instanceName
85
- : options.instanceName;
86
- }
87
- // run
88
- const ctx = this.app.createAnonymousContext(options.req, options.res);
89
- return await this.app.ctxStorage.run(ctx, async () => {
81
+ const capture = captureRequestBoundary();
82
+ const onScopeOutcome = lifecycle?.onScopeOutcome;
83
+ let ctx: VonaContext | undefined;
84
+ try {
85
+ options = Object.assign({}, options);
90
86
  // innerAccess
91
- ctx.innerAccess = innerAccess;
92
- // locale
93
- if (options.locale !== undefined) {
94
- ctx.locale = options.locale;
87
+ const innerAccess = options.innerAccess !== false;
88
+ // isolate
89
+ const isolate =
90
+ !this.ctx ||
91
+ options.instanceName !== undefined ||
92
+ (!innerAccess && this.ctx?.instanceName === null);
93
+ const ctxCaller = !isolate && this.ctx ? this.ctx : undefined;
94
+ // locale/tz/instanceName
95
+ if (!isolate && this.ctx) {
96
+ options.locale = options.locale === undefined ? this.ctx.locale : options.locale;
97
+ options.tz = options.tz === undefined ? this.ctx.tz : options.tz;
98
+ options.instanceName =
99
+ options.instanceName === undefined
100
+ ? !innerAccess && this.ctx.instanceName === null
101
+ ? undefined
102
+ : this.ctx.instanceName
103
+ : options.instanceName;
95
104
  }
96
- // tz
97
- if (options.tz !== undefined) {
98
- ctx.tz = options.tz;
99
- }
100
- // instanceName: undefined/null is different
101
- if (options.instanceName !== undefined) {
102
- ctx.instanceName = options.instanceName;
103
- }
104
- // ctxCaller
105
- if (ctxCaller) {
106
- // delegateProperties
107
- __delegateProperties(ctx, ctxCaller);
105
+ // run
106
+ ctx = this.app.createAnonymousContext(options.req, options.res);
107
+ const context = ctx;
108
+ const execute = async () => {
109
+ const initialized: unknown = lifecycle?.initializeContext?.(context);
110
+ if (initialized && (typeof initialized === 'object' || typeof initialized === 'function')) {
111
+ const then = (initialized as PromiseLike<unknown>).then;
112
+ if (typeof then === 'function') {
113
+ await new Promise<unknown>((resolve, reject) => {
114
+ then.call(initialized, resolve, reject);
115
+ }).catch(() => undefined);
116
+ throw new Error('Context initializer must be synchronous');
117
+ }
118
+ }
119
+ // innerAccess
120
+ context.innerAccess = innerAccess;
121
+ // locale
122
+ if (options.locale !== undefined) {
123
+ context.locale = options.locale;
124
+ }
125
+ // tz
126
+ if (options.tz !== undefined) {
127
+ context.tz = options.tz;
128
+ }
129
+ // instanceName: undefined/null is different
130
+ if (options.instanceName !== undefined) {
131
+ context.instanceName = options.instanceName;
132
+ }
108
133
  // ctxCaller
109
- ctx.ctxCaller = ctxCaller;
110
- } else {
111
- // isolate: extraData
112
- if (options.extraData) {
134
+ if (ctxCaller) {
113
135
  // delegateProperties
114
- __delegateProperties(ctx, options.extraData);
136
+ __delegateProperties(context, ctxCaller);
137
+ // ctxCaller
138
+ context.ctxCaller = ctxCaller;
139
+ } else {
140
+ // isolate: extraData
141
+ if (options.extraData) {
142
+ // delegateProperties
143
+ __delegateProperties(context, options.extraData);
144
+ }
145
+ }
146
+ return await this.bean._getBean(ServiceTransactionAsyncLocalStorage).run(fn, isolate);
147
+ };
148
+ return onScopeOutcome
149
+ ? await this.app.ctxStorage.runObserved(context, execute, onScopeOutcome)
150
+ : await this.app.ctxStorage.run(context, execute);
151
+ } catch (error) {
152
+ if (!ctx && onScopeOutcome) {
153
+ try {
154
+ const result: unknown = onScopeOutcome('not_created');
155
+ if (result && (typeof result === 'object' || typeof result === 'function')) {
156
+ const then = (result as PromiseLike<unknown>).then;
157
+ if (typeof then === 'function') {
158
+ capture?.reportFailure('observer_failed');
159
+ void new Promise<unknown>((resolve, reject) => {
160
+ then.call(result, resolve, reject);
161
+ }).catch(() => {});
162
+ }
163
+ }
164
+ } catch {
165
+ capture?.reportFailure('observer_failed');
115
166
  }
116
167
  }
117
- return await this.bean._getBean(ServiceTransactionAsyncLocalStorage).run(fn, isolate);
118
- });
168
+ throw error;
169
+ }
119
170
  }
120
171
 
121
172
  private async _newCtxInnerStep2<RESULT>(
@@ -1,7 +1,7 @@
1
1
  import type { IApiPathRecordMethodMap } from 'vona-module-a-web';
2
2
 
3
3
  import { combineParamsAndQuery } from '@cabloy/utils';
4
- import { BeanBase, cast, deepExtend } from 'vona';
4
+ import { BeanBase, cast, deepExtend, getRequestBoundary } from 'vona';
5
5
  import { Aspect } from 'vona-module-a-aspect';
6
6
  import { Service } from 'vona-module-a-bean';
7
7
 
@@ -19,8 +19,8 @@ export class ServiceExecutor extends BeanBase {
19
19
  ): Promise<any> {
20
20
  // app
21
21
  const app = this.app;
22
- const telemetry = this.$scope.telemetry.service.telemetry;
23
- const span = telemetry.enabled
22
+ const telemetry = getRequestBoundary() ? undefined : this.$scope.telemetry.service.telemetry;
23
+ const span = telemetry?.enabled
24
24
  ? telemetry.startSpan(`vona.action ${method.toUpperCase()}`, {
25
25
  attributes: { 'vona.action.internal': true, 'http.request.method': method.toUpperCase() },
26
26
  })
@@ -83,16 +83,16 @@ export class ServiceExecutor extends BeanBase {
83
83
  state: {
84
84
  ...options?.extraData?.state,
85
85
  telemetry: span
86
- ? { context: telemetry.createContext(span), span, internalAction: true }
86
+ ? { context: telemetry!.createContext(span), span, internalAction: true }
87
87
  : undefined,
88
88
  },
89
89
  },
90
90
  },
91
91
  );
92
92
  try {
93
- return await (span ? telemetry.withSpan(span, execute) : execute());
93
+ return await (span ? telemetry!.withSpan(span, execute) : execute());
94
94
  } catch (err) {
95
- if (span) telemetry.recordException(span, err);
95
+ if (span) telemetry!.recordException(span, err);
96
96
  throw err;
97
97
  } finally {
98
98
  span?.end();
@@ -1,4 +1,11 @@
1
- import type { ContextState, IInstanceRecord, ILocaleRecord, PowerPartial } from 'vona';
1
+ import type {
2
+ ContextState,
3
+ IInstanceRecord,
4
+ ILocaleRecord,
5
+ PowerPartial,
6
+ VonaContext,
7
+ VonaScopeOutcomeObserver,
8
+ } from 'vona';
2
9
  import type { ConfigOnions } from 'vona-module-a-onion';
3
10
  import type { IDbInfo, ITransactionOptions } from 'vona-module-a-orm';
4
11
 
@@ -15,6 +22,11 @@ export interface INewCtxOptions extends INewCtxBaseOptions {
15
22
  dbInfo?: Partial<IDbInfo>;
16
23
  }
17
24
 
25
+ export interface INewCtxLifecycle {
26
+ initializeContext?: (ctx: VonaContext) => void;
27
+ onScopeOutcome?: VonaScopeOutcomeObserver;
28
+ }
29
+
18
30
  export interface INewCtxBaseOptions {
19
31
  locale?: keyof ILocaleRecord;
20
32
  tz?: string;
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-jwt",
3
- "version": "5.1.18",
3
+ "version": "5.1.19",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -0,0 +1,182 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readFileSync } from 'node:fs';
3
+ import { registerHooks } from 'node:module';
4
+ import { test } from 'node:test';
5
+ import ts from 'typescript';
6
+
7
+ const previousApp = Object.getOwnPropertyDescriptor(globalThis, '__app__');
8
+ const vonaUrl = new URL('../../../../../../../', import.meta.url);
9
+ const clientUrl = new URL('../service/jwtClient.ts?fixture=jwtLogging', import.meta.url).href;
10
+ const formatterUrl = new URL('packages-vona/vona-core/src/lib/core/logger/utils.ts', vonaUrl).href;
11
+ const checkUrl = new URL('packages-utils/utils/src/check.ts', vonaUrl).href;
12
+ const utilsUrl = new URL('packages-utils/utils/src/utils.ts', vonaUrl).href;
13
+ const castUrl = new URL('packages-vona/vona-core/src/types/utils/cast.ts', vonaUrl).href;
14
+ const fixture = (source: string) => `data:text/javascript,${encodeURIComponent(source)}`;
15
+ const frameworkFixture = fixture(`
16
+ export class BeanBase {}
17
+ export { cast } from ${JSON.stringify(castUrl)};
18
+ export function deepExtend() { throw new Error('unexpected client initialization'); }
19
+ `);
20
+ const utilityFixture = fixture(`
21
+ export { isNil } from ${JSON.stringify(checkUrl)};
22
+ export { catchError } from ${JSON.stringify(utilsUrl)};
23
+ `);
24
+ const hooks = registerHooks({
25
+ resolve(specifier, context, nextResolve) {
26
+ if (context.parentURL === clientUrl) {
27
+ if (specifier === 'vona') return { url: frameworkFixture, shortCircuit: true };
28
+ if (specifier === '@cabloy/utils') return { url: utilityFixture, shortCircuit: true };
29
+ if (specifier === 'vona-module-a-bean') {
30
+ return {
31
+ url: fixture('export function Service() { return () => {}; }'),
32
+ shortCircuit: true,
33
+ };
34
+ }
35
+ }
36
+ if (context.parentURL === formatterUrl) {
37
+ if (specifier === '@cabloy/utils') return { url: checkUrl, shortCircuit: true };
38
+ if (specifier === '../../framework/useApp.ts') {
39
+ return {
40
+ url: fixture(
41
+ 'export function useApp() { throw new Error("unexpected application access"); }',
42
+ ),
43
+ shortCircuit: true,
44
+ };
45
+ }
46
+ }
47
+ return nextResolve(specifier, context);
48
+ },
49
+ load(url, context, nextLoad) {
50
+ if (url === clientUrl) {
51
+ return {
52
+ format: 'module',
53
+ source: ts.transpileModule(readFileSync(new URL(url), 'utf8'), {
54
+ compilerOptions: {
55
+ experimentalDecorators: true,
56
+ target: ts.ScriptTarget.ESNext,
57
+ module: ts.ModuleKind.ESNext,
58
+ },
59
+ }).outputText,
60
+ shortCircuit: true,
61
+ };
62
+ }
63
+ return nextLoad(url, context);
64
+ },
65
+ });
66
+ const { ServiceJwtClient, formatLoggerFilter } = await (async () => {
67
+ try {
68
+ return { ...(await import(clientUrl)), ...(await import(formatterUrl)) };
69
+ } finally {
70
+ hooks.deregister();
71
+ }
72
+ })();
73
+
74
+ const secret = 'synthetic-jwt-secret-sentinel';
75
+
76
+ for (const operation of ['sign', 'verify'] as const) {
77
+ for (const failure of [false, true]) {
78
+ test(`actual JWT ${operation} debug log, ${failure ? 'error' : 'success'}`, async () => {
79
+ const output: string[] = [];
80
+ const filter = formatLoggerFilter({ level: 'debug', child: undefined });
81
+ const error = new Error(secret);
82
+ const payloadData = Object.freeze({ value: secret });
83
+ const options = Object.freeze({ path: '/callback', expiresIn: 60 });
84
+ const signOptions = Object.freeze({ issuer: 'synthetic-test' });
85
+ const verifyOptions = Object.freeze({ issuer: 'synthetic-test' });
86
+ let calls = 0;
87
+ const fake: any = {
88
+ _clientName: 'access',
89
+ _clientOptions: { secret, signOptions, verifyOptions },
90
+ fieldClient: 'client',
91
+ fieldPath: 'path',
92
+ fieldData: 'data',
93
+ _signInner: ServiceJwtClient.prototype['_signInner'],
94
+ _verifyInner: ServiceJwtClient.prototype['_verifyInner'],
95
+ _checkVerifyPath: ServiceJwtClient.prototype._checkVerifyPath,
96
+ scope: { service: { jwtExtract: { fromAllWays: () => secret } } },
97
+ app: {
98
+ throw: () => {
99
+ throw new Error('unexpected authentication change');
100
+ },
101
+ },
102
+ $loggerChild: (name: string) => {
103
+ assert.equal(name, 'jwt');
104
+ return {
105
+ debug: (message: any) => {
106
+ const info = filter.transform({ level: 'debug', message }, filter.options);
107
+ assert.ok(info);
108
+ output.push(JSON.stringify(info));
109
+ },
110
+ };
111
+ },
112
+ _jwtInstance: {
113
+ sign: (payload: any, key: string, passedOptions: any, callback: any) => {
114
+ calls++;
115
+ assert.equal(key, secret);
116
+ assert.equal(payload.data, payloadData);
117
+ assert.equal(payload.client, 'access');
118
+ assert.equal(payload.path, '/callback');
119
+ assert.deepEqual(passedOptions, { ...signOptions, expiresIn: 60 });
120
+ callback(failure ? error : null, failure ? undefined : secret);
121
+ },
122
+ verify: (token: string, key: string, passedOptions: any, callback: any) => {
123
+ calls++;
124
+ assert.equal(token, secret);
125
+ assert.equal(key, secret);
126
+ assert.equal(passedOptions, verifyOptions);
127
+ callback(
128
+ failure ? error : null,
129
+ failure
130
+ ? undefined
131
+ : {
132
+ client: 'access',
133
+ path: '/callback',
134
+ data: payloadData,
135
+ },
136
+ );
137
+ },
138
+ },
139
+ };
140
+ const execution =
141
+ operation === 'sign'
142
+ ? ServiceJwtClient.prototype.sign.call(fake, payloadData, options)
143
+ : ServiceJwtClient.prototype.verify.call(fake, secret, options);
144
+ if (failure) {
145
+ await assert.rejects(execution, value => value === error);
146
+ } else {
147
+ assert.equal(await execution, operation === 'sign' ? secret : payloadData);
148
+ }
149
+ assert.equal(calls, 1);
150
+ assert.equal(output.length, 1);
151
+ assert.equal(output.join('\n').includes(secret), false);
152
+ assert.equal(
153
+ JSON.parse(output[0]!).message,
154
+ `jwt.${operation}: ${failure ? 'failed' : 'completed'}`,
155
+ );
156
+ assert.deepEqual(Object.getOwnPropertyDescriptor(globalThis, '__app__'), previousApp);
157
+ });
158
+ }
159
+ }
160
+
161
+ test('JWT extraction and missing-token behavior retain safe debug diagnostics', async () => {
162
+ const output: string[] = [];
163
+ let extracted = 0;
164
+ const fake = {
165
+ _clientName: 'access',
166
+ scope: {
167
+ service: {
168
+ jwtExtract: {
169
+ fromAllWays: () => {
170
+ extracted++;
171
+ return undefined;
172
+ },
173
+ },
174
+ },
175
+ },
176
+ _verifyInner: ServiceJwtClient.prototype['_verifyInner'],
177
+ $loggerChild: () => ({ debug: (message: () => string) => output.push(message()) }),
178
+ };
179
+ assert.equal(await ServiceJwtClient.prototype.verify.call(fake as any), undefined);
180
+ assert.equal(extracted, 1);
181
+ assert.deepEqual(output, ['jwt.verify: completed']);
182
+ });
@@ -53,10 +53,7 @@ export class ServiceJwtClient extends BeanBase {
53
53
  const [res, error] = await catchError(() => {
54
54
  return this._signInner(payloadData, options);
55
55
  });
56
- this.$loggerChild('jwt').debug(
57
- () =>
58
- `jwt.sign: client:${this._clientName}, token:${res}${error ? `, error: ${error.message}` : ''}`,
59
- );
56
+ this.$loggerChild('jwt').debug(() => `jwt.sign: ${error ? 'failed' : 'completed'}`);
60
57
  if (error) throw error;
61
58
  return res;
62
59
  }
@@ -99,10 +96,7 @@ export class ServiceJwtClient extends BeanBase {
99
96
  const [res, error] = await catchError(() => {
100
97
  return this._verifyInner(token, options);
101
98
  });
102
- this.$loggerChild('jwt').debug(
103
- () =>
104
- `jwt.verify: client:${this._clientName}, token:${token}${error ? `, error: ${error.message}` : ''}`,
105
- );
99
+ this.$loggerChild('jwt').debug(() => `jwt.verify: ${error ? 'failed' : 'completed'}`);
106
100
  if (error) throw error;
107
101
  return res;
108
102
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-logger",
3
- "version": "5.1.16",
3
+ "version": "5.1.17",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -8,7 +8,7 @@ import type {
8
8
  import type winston from 'winston';
9
9
 
10
10
  import { evaluateExpressions } from '@cabloy/utils';
11
- import { BeanAopMethodBase, SymbolBeanFullName } from 'vona';
11
+ import { BeanAopMethodBase, getRequestBoundary, SymbolBeanFullName } from 'vona';
12
12
  import { AopMethod } from 'vona-module-a-aspect';
13
13
 
14
14
  export interface IAopMethodOptionsLog extends IDecoratorAopMethodOptions {
@@ -28,6 +28,7 @@ export class AopMethodLog
28
28
  implements IAopMethodGet, IAopMethodSet, IAopMethodExecute
29
29
  {
30
30
  get(options: IAopMethodOptionsLog, next: NextSync, receiver: any, prop: string): any {
31
+ if (getRequestBoundary()) return next();
31
32
  const context = this._getContext(options, receiver);
32
33
  const message = `${receiver[SymbolBeanFullName]}#${prop}(get)`;
33
34
  const logger = this.app.meta.logger.child(options.childName, options.clientName);
@@ -51,6 +52,7 @@ export class AopMethodLog
51
52
  receiver: any,
52
53
  prop: string,
53
54
  ): boolean {
55
+ if (getRequestBoundary()) return next();
54
56
  const context = this._getContext(options, receiver);
55
57
  const message = `${receiver[SymbolBeanFullName]}#${prop}(set)`;
56
58
  const logger = this.app.meta.logger.child(options.childName, options.clientName);
@@ -74,6 +76,7 @@ export class AopMethodLog
74
76
  receiver: any,
75
77
  prop: string,
76
78
  ): Promise<any> | any {
79
+ if (getRequestBoundary()) return next();
77
80
  const context = this._getContext(options, receiver);
78
81
  const message = `${receiver[SymbolBeanFullName]}#${prop}`;
79
82
  const logger = this.app.meta.logger.child(options.childName, options.clientName);
@@ -9,11 +9,14 @@ import type * as Transport from 'winston-transport';
9
9
 
10
10
  import {
11
11
  BeanBase,
12
+ formatLoggerBoundary,
12
13
  formatLoggerConsole,
13
14
  formatLoggerCtx,
14
15
  formatLoggerDummy,
15
16
  formatLoggerErrors,
16
17
  formatLoggerFilter,
18
+ getRequestBoundary,
19
+ getSuppressedDiagnosticLogger,
17
20
  } from 'vona';
18
21
  import { Bean } from 'vona-module-a-bean';
19
22
  import * as Winston from 'winston';
@@ -39,10 +42,12 @@ export class BeanLogger extends BeanBase {
39
42
  }
40
43
 
41
44
  public get default(): Winston.Logger {
45
+ if (getRequestBoundary()) return getSuppressedDiagnosticLogger();
42
46
  return this.app.meta.logger.get();
43
47
  }
44
48
 
45
49
  public get(clientName?: keyof ILoggerClientRecord): Winston.Logger {
50
+ if (getRequestBoundary()) return getSuppressedDiagnosticLogger();
46
51
  return this.app.meta.logger.get(clientName);
47
52
  }
48
53
 
@@ -50,11 +55,13 @@ export class BeanLogger extends BeanBase {
50
55
  childName: keyof ILoggerChildRecord,
51
56
  clientName?: keyof ILoggerClientRecord,
52
57
  ): Winston.Logger {
58
+ if (getRequestBoundary()) return getSuppressedDiagnosticLogger();
53
59
  return this.app.meta.logger.get(clientName).child({ name: childName });
54
60
  }
55
61
 
56
62
  public makeFormatBase(opts: ILoggerFormatOpts) {
57
63
  return Winston.format.combine(
64
+ formatLoggerBoundary(),
58
65
  formatLoggerErrors(opts),
59
66
  formatLoggerCtx(),
60
67
  Winston.format.splat(),
@@ -71,6 +78,7 @@ export class BeanLogger extends BeanBase {
71
78
  return this.app.meta.logger.createTransportFile(fileName, clientInfo, {
72
79
  level: levelStrict ?? 'silly',
73
80
  format: Winston.format.combine(
81
+ formatLoggerBoundary(),
74
82
  formatLoggerFilter({
75
83
  child: childStrict ?? clientInfo.child,
76
84
  level: levelStrict ?? clientInfo.level,
@@ -85,6 +93,7 @@ export class BeanLogger extends BeanBase {
85
93
  return new Winston.transports.Console({
86
94
  level: 'silly',
87
95
  format: Winston.format.combine(
96
+ formatLoggerBoundary(),
88
97
  formatLoggerDummy(),
89
98
  formatLoggerFilter({ child: clientInfo.child, level: clientInfo.level, silly: true }),
90
99
  Winston.format.colorize(),
@@ -5,9 +5,10 @@ import type {
5
5
  } from 'vona-module-a-aspect';
6
6
 
7
7
  import StdSerializers from 'pino-std-serializers';
8
- import { BeanBase } from 'vona';
8
+ import { BeanBase, getRequestBoundary } from 'vona';
9
9
  import { MiddlewareSystem } from 'vona-module-a-aspect';
10
10
 
11
+ import { redactHttpLogHeaders, redactHttpLogUrl } from '../lib/redactHttpLog.ts';
11
12
  import { redactIsolatedReadyHeader } from '../lib/redactIsolatedReadyHeader.ts';
12
13
 
13
14
  export interface IMiddlewareSystemOptionsHttpLog extends IDecoratorMiddlewareSystemOptions {}
@@ -15,28 +16,40 @@ export interface IMiddlewareSystemOptionsHttpLog extends IDecoratorMiddlewareSys
15
16
  @MiddlewareSystem<IMiddlewareSystemOptionsHttpLog>()
16
17
  export class MiddlewareSystemHttpLog extends BeanBase implements IMiddlewareSystemExecute {
17
18
  async execute(_options: IMiddlewareSystemOptionsHttpLog, next: Next) {
19
+ if (getRequestBoundary()) return next();
18
20
  const ctx = this.ctx;
19
21
  // start
20
- const req = StdSerializers.req(ctx.req);
21
- req.headers = redactIsolatedReadyHeader(
22
- req.headers,
23
- ctx.path,
24
- this.app.meta.env.VONA_TEST_RUN_ID,
25
- ['x-vona-test-challenge', 'x-vona-test-stop-nonce', 'x-vona-test-stop-authorization'],
26
- );
22
+ const serialized = StdSerializers.req(ctx.req);
23
+ // Do not retain serializer query/params/raw extensions or request bodies in logs.
24
+ const req = {
25
+ method: serialized.method,
26
+ url: redactHttpLogUrl(serialized.url),
27
+ headers: redactHttpLogHeaders(
28
+ redactIsolatedReadyHeader(
29
+ serialized.headers,
30
+ ctx.path,
31
+ this.app.meta.env.VONA_TEST_RUN_ID,
32
+ ['x-vona-test-challenge', 'x-vona-test-stop-nonce', 'x-vona-test-stop-authorization'],
33
+ ),
34
+ ),
35
+ remoteAddress: serialized.remoteAddress,
36
+ remotePort: serialized.remotePort,
37
+ };
27
38
  this.$loggerChild('req').http(() => JSON.stringify(req));
28
39
  const profiler = this.$loggerChild('res').startTimer();
29
40
  // next
30
41
  await next();
31
42
  // end
32
- const headers = redactIsolatedReadyHeader(
33
- ctx.res.getHeaders(),
34
- ctx.path,
35
- this.app.meta.env.VONA_TEST_RUN_ID,
36
- ['x-vona-test-proof', 'x-vona-test-stop-proof'],
43
+ const headers = redactHttpLogHeaders(
44
+ redactIsolatedReadyHeader(
45
+ ctx.res.getHeaders(),
46
+ ctx.path,
47
+ this.app.meta.env.VONA_TEST_RUN_ID,
48
+ ['x-vona-test-proof', 'x-vona-test-stop-proof'],
49
+ ),
37
50
  );
38
51
  const res = {
39
- url: ctx.req.url,
52
+ url: redactHttpLogUrl(ctx.req.url),
40
53
  statusCode: ctx.res.statusCode,
41
54
  headers,
42
55
  };