cabloy 5.1.219 → 5.1.221

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 (89) hide show
  1. package/.cabloy-version +1 -1
  2. package/.github/workflows/agent-governance.yml +2 -2
  3. package/.github/workflows/create-cabloy.yml +3 -3
  4. package/.github/workflows/docs-pages.yml +2 -2
  5. package/.github/workflows/playwright-e2e.yml +4 -4
  6. package/.github/workflows/vona-cov-pg.yml +2 -2
  7. package/.github/workflows/vona-test-crud.yml +2 -2
  8. package/.github/workflows/vona-test-mysql.yml +2 -2
  9. package/.github/workflows/vona-test-pg.yml +2 -2
  10. package/.github/workflows/vona-test-sqlite3.yml +2 -2
  11. package/.github/workflows/vona-tsc.yml +2 -2
  12. package/.github/workflows/zova-ui.yml +2 -2
  13. package/CHANGELOG.md +23 -0
  14. package/README.md +1 -1
  15. package/package.json +4 -3
  16. package/repo-docs/frontend/openapi-sdk-guide.md +2 -0
  17. package/repo-docs/fullstack/quickstart.md +3 -3
  18. package/repo-docs/reference/repo-scripts.md +16 -0
  19. package/scripts/generateOpenapiIsolated.test.ts +25 -0
  20. package/scripts/generateOpenapiIsolated.ts +46 -12
  21. package/scripts/runIsolatedTest.test.ts +165 -45
  22. package/scripts/runIsolatedTest.ts +111 -24
  23. package/scripts/strictGenerationDiagnostics.test.ts +1009 -0
  24. package/scripts/strictGenerationDiagnostics.ts +61 -0
  25. package/scripts/strictGenerationStartup.test.ts +212 -0
  26. package/vona/package.original.json +3 -2
  27. package/vona/packages-cli/cli/package.json +1 -1
  28. package/vona/packages-cli/cli-set-api/package.json +1 -1
  29. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dev.ts +14 -4
  30. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/generateEntryFiles.ts +6 -1
  31. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/generateVonaMeta.ts +2 -10
  32. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/validateGenerationPaths.ts +27 -0
  33. package/vona/packages-cli/cli-set-api/test/binDevLifecycle.test.ts +1 -1
  34. package/vona/packages-cli/cli-set-api/test/strictGenerationPrivacy.test.ts +439 -0
  35. package/vona/packages-vona/vona/package.json +1 -1
  36. package/vona/packages-vona/vona-core/package.json +1 -1
  37. package/vona/packages-vona/vona-core/src/lib/bean/beanBase.ts +4 -0
  38. package/vona/packages-vona/vona-core/src/lib/core/asyncLocalStorage.ts +84 -12
  39. package/vona/packages-vona/vona-core/src/lib/core/index.ts +14 -0
  40. package/vona/packages-vona/vona-core/src/lib/core/logger/appLogger.ts +19 -3
  41. package/vona/packages-vona/vona-core/src/lib/core/logger/diagnosticLogger.test.ts +346 -0
  42. package/vona/packages-vona/vona-core/src/lib/core/logger/diagnosticLogger.ts +138 -0
  43. package/vona/packages-vona/vona-core/src/lib/core/logger/index.ts +1 -0
  44. package/vona/packages-vona/vona-core/src/lib/core/logger/utils.ts +25 -5
  45. package/vona/packages-vona/vona-core/src/lib/core/requestBoundary.ts +89 -0
  46. package/vona/packages-vona/vona-core/src/lib/core/testFreshness.test.ts +379 -0
  47. package/vona/packages-vona/vona-core/src/lib/core/testFreshness.ts +232 -0
  48. package/vona/packages-vona/vona-core/src/lib/core/testStrictMode.ts +9 -0
  49. package/vona/packages-vona/vona-core/src/lib/framework/start.test.ts +4 -3
  50. package/vona/packages-vona/vona-core/src/lib/framework/start.ts +2 -0
  51. package/vona/packages-vona/vona-mock/package.json +1 -1
  52. package/vona/pnpm-lock.yaml +691 -356
  53. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  54. package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
  55. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/bean/bean.executor.ts +111 -60
  56. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/service/executor.ts +6 -6
  57. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/types/executor.ts +13 -1
  58. package/vona/src/suite-vendor/a-vona/modules/a-jwt/package.json +1 -1
  59. package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/lib/jwtClientLogging.test.ts +182 -0
  60. package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/service/jwtClient.ts +2 -8
  61. package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
  62. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/aopMethod.log.ts +4 -1
  63. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/bean.logger.ts +9 -0
  64. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/middlewareSystem.httpLog.ts +27 -14
  65. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactHttpLog.test.ts +207 -0
  66. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactHttpLog.ts +30 -0
  67. package/vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts +33 -1
  68. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/package.json +1 -1
  69. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/index.ts +11 -0
  70. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/lib/protectedSqliteRegistry.ts +356 -0
  71. package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
  72. package/vona/src/suite-vendor/a-vona/modules/a-startup/src/service/startup.ts +6 -0
  73. package/vona/src/suite-vendor/a-vona/modules/a-startup/test/devSeed.test.ts +2 -2
  74. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/package.json +1 -1
  75. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/bean.telemetry.ts +8 -1
  76. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/middlewareSystem.trace.ts +9 -14
  77. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/lib/spanDiagnostics.ts +70 -0
  78. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/service/telemetry.ts +37 -23
  79. package/vona/src/suite-vendor/a-vona/modules/a-version/package.json +1 -1
  80. package/vona/src/suite-vendor/a-vona/modules/a-version/src/service/database.ts +31 -4
  81. package/vona/src/suite-vendor/a-vona/package.json +2 -2
  82. package/zova/package.original.json +3 -2
  83. package/zova/packages-cli/cli/package.json +2 -2
  84. package/zova/packages-cli/cli-set-front/package.json +1 -1
  85. package/zova/packages-cli/cli-set-front/src/lib/bean/cli.bin.buildRest.ts +11 -101
  86. package/zova/packages-cli/cli-set-front/src/lib/common/restDependencies.ts +78 -0
  87. package/zova/packages-cli/cli-set-front/src/lib/common/restDependencyVersions.ts +252 -0
  88. package/zova/packages-cli/cli-set-front/test/restDependencies.test.ts +568 -0
  89. package/zova/pnpm-lock.yaml +442 -564
@@ -0,0 +1,346 @@
1
+ import assert from 'node:assert/strict';
2
+ import { afterEach, beforeEach, test } from 'node:test';
3
+ import * as Winston from 'winston';
4
+ import Transport from 'winston-transport';
5
+
6
+ import { createRequestBoundary, runWithRequestBoundary } from '../requestBoundary.ts';
7
+ import { requiresFreshTestResources, TEST_REQUIRE_FRESH_RESOURCES_ENV } from '../testStrictMode.ts';
8
+ import { AppLogger } from './appLogger.ts';
9
+ import { getSuppressedDiagnosticLogger } from './diagnosticLogger.ts';
10
+
11
+ let previousStrictFlag: string | undefined;
12
+ beforeEach(() => {
13
+ previousStrictFlag = process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
14
+ delete process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
15
+ });
16
+ afterEach(() => {
17
+ if (previousStrictFlag === undefined) delete process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
18
+ else process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = previousStrictFlag;
19
+ });
20
+
21
+ class MemoryTransport extends Transport {
22
+ count = 0;
23
+
24
+ override log(_info: unknown, callback: () => void) {
25
+ this.count++;
26
+ callback();
27
+ }
28
+ }
29
+
30
+ // No boundary formatter: only the facade can prevent Winston from reading input.
31
+ function fixture() {
32
+ const sink = new MemoryTransport();
33
+ const raw = Winston.createLogger({ transports: [sink] });
34
+ const factory = new AppLogger();
35
+ let creations = 0;
36
+ Object.defineProperty(factory, '_createClient', {
37
+ value() {
38
+ creations++;
39
+ return raw;
40
+ },
41
+ });
42
+ const logger = factory.get();
43
+ return {
44
+ raw,
45
+ logger,
46
+ sink,
47
+ factory,
48
+ creations: () => creations,
49
+ async close() {
50
+ try {
51
+ await factory.dispose();
52
+ } finally {
53
+ raw.close();
54
+ sink.close?.();
55
+ }
56
+ },
57
+ };
58
+ }
59
+
60
+ function payloadProbe() {
61
+ let reads = 0;
62
+ return {
63
+ payload: {
64
+ get message() {
65
+ reads++;
66
+ return 'local';
67
+ },
68
+ },
69
+ reads: () => reads,
70
+ };
71
+ }
72
+
73
+ function throwingProbe() {
74
+ let reads = 0;
75
+ const read = () => {
76
+ reads++;
77
+ throw new Error('Synthetic logger input must not be evaluated');
78
+ };
79
+ return {
80
+ value: new Proxy({}, { get: read, ownKeys: read }),
81
+ read,
82
+ reads: () => reads,
83
+ };
84
+ }
85
+
86
+ test(
87
+ 'cached diagnostic handlers never expose raw Winston to a request boundary',
88
+ { concurrency: false },
89
+ async () => {
90
+ const local = fixture();
91
+ const { raw, logger, sink } = local;
92
+ const exceptions = logger.exceptions;
93
+ const rejections = logger.rejections;
94
+ const probe = payloadProbe();
95
+ try {
96
+ assert.equal(local.factory.get(), logger);
97
+ assert.equal(local.creations(), 1);
98
+ assert.equal(logger.exceptions, exceptions);
99
+ assert.equal(logger.rejections, rejections);
100
+ for (const handler of [exceptions, rejections]) {
101
+ assert.equal(handler.logger, logger);
102
+ assert.equal(Reflect.set(handler, 'logger', raw), false);
103
+ }
104
+ assert.equal(raw.exceptions.logger, raw);
105
+ assert.equal(raw.rejections.logger, raw);
106
+ runWithRequestBoundary(createRequestBoundary(), () => {
107
+ assert.equal(local.factory.get(), getSuppressedDiagnosticLogger());
108
+ assert.equal(local.factory.get('unconfigured' as any), getSuppressedDiagnosticLogger());
109
+ assert.equal(local.factory.child('synthetic' as any), getSuppressedDiagnosticLogger());
110
+ logger.error(probe.payload);
111
+ logger.exceptions.logger.error(probe.payload);
112
+ logger.rejections.logger.error(probe.payload);
113
+ exceptions.logger.error(probe.payload);
114
+ rejections.logger.error(probe.payload);
115
+ });
116
+ assert.equal(probe.reads(), 0);
117
+ assert.equal(sink.count, 0);
118
+ for (const handler of [exceptions, rejections]) {
119
+ const before = probe.reads();
120
+ handler.logger.error(probe.payload);
121
+ assert.ok(probe.reads() > before);
122
+ }
123
+ assert.equal(sink.count, 2);
124
+ const before = probe.reads();
125
+ raw.error(probe.payload);
126
+ assert.ok(probe.reads() > before);
127
+ assert.equal(sink.count, 3);
128
+ } finally {
129
+ await local.close();
130
+ }
131
+ },
132
+ );
133
+
134
+ test(
135
+ 'diagnostic handler methods keep their raw receiver and map fluent returns',
136
+ { concurrency: false },
137
+ async () => {
138
+ const local = fixture();
139
+ try {
140
+ for (const key of ['exceptions', 'rejections'] as const) {
141
+ const rawHandler = local.raw[key];
142
+ const handler = local.logger[key];
143
+ const marker = {};
144
+ const original = rawHandler.getAllInfo;
145
+ Object.defineProperty(rawHandler, 'getAllInfo', {
146
+ configurable: true,
147
+ value(this: typeof rawHandler, value: unknown) {
148
+ assert.equal(this, rawHandler);
149
+ assert.equal(value, marker);
150
+ return this;
151
+ },
152
+ });
153
+ try {
154
+ assert.equal(handler.handlers, rawHandler.handlers);
155
+ assert.equal(handler.getAllInfo(marker as any), handler);
156
+ assert.equal(handler.logger, local.logger);
157
+ } finally {
158
+ Object.defineProperty(rawHandler, 'getAllInfo', {
159
+ configurable: true,
160
+ value: original,
161
+ });
162
+ }
163
+ }
164
+ const exceptions = local.logger.exceptions;
165
+ const rejections = local.logger.rejections;
166
+ local.logger.configure({ transports: [local.sink] });
167
+ assert.notEqual(local.logger.exceptions, exceptions);
168
+ assert.notEqual(local.logger.rejections, rejections);
169
+ assert.equal(local.logger.exceptions.logger, local.logger);
170
+ assert.equal(local.logger.rejections.logger, local.logger);
171
+ assert.equal(exceptions.logger, local.logger);
172
+ assert.equal(rejections.logger, local.logger);
173
+ runWithRequestBoundary(createRequestBoundary(), () => {
174
+ exceptions.logger.error('local');
175
+ rejections.logger.error('local');
176
+ });
177
+ assert.equal(local.sink.count, 0);
178
+ } finally {
179
+ await local.close();
180
+ }
181
+ },
182
+ );
183
+
184
+ test('strict logger bypasses cached clients and payloads', { concurrency: false }, async () => {
185
+ const local = fixture();
186
+ const probe = throwingProbe();
187
+ try {
188
+ process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = 'true';
189
+ const suppressed = getSuppressedDiagnosticLogger();
190
+ assert.equal(local.factory.get(), suppressed);
191
+ assert.equal(local.factory.get('unconfigured' as any), suppressed);
192
+ assert.equal(local.factory.child('synthetic' as any), suppressed);
193
+ assert.equal(local.factory.child('synthetic' as any, 'unconfigured' as any), suppressed);
194
+ assert.notEqual(suppressed, local.logger);
195
+ assert.equal(suppressed.error(probe.value), suppressed);
196
+ assert.equal(suppressed.info(probe.value), suppressed);
197
+ assert.equal(suppressed.log(probe.value as any), suppressed);
198
+ assert.equal(suppressed.child(probe.value), suppressed);
199
+ assert.equal(suppressed.startTimer().done(probe.value), suppressed);
200
+ let completions = 0;
201
+ assert.equal(
202
+ suppressed.write(probe.value, () => completions++),
203
+ true,
204
+ );
205
+ assert.equal(completions, 1);
206
+ assert.equal(probe.reads(), 0);
207
+ assert.equal(local.creations(), 1);
208
+ assert.equal(local.sink.count, 0);
209
+ delete process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
210
+ assert.equal(local.factory.get(), local.logger);
211
+ local.factory.child().info('ordinary logging remains available');
212
+ assert.equal(local.sink.count, 1);
213
+ } finally {
214
+ await local.close();
215
+ }
216
+ });
217
+
218
+ test('strict entry guards precede cache and app access', { concurrency: false }, async () => {
219
+ const local = fixture();
220
+ const probe = throwingProbe();
221
+ const descriptors = Object.getOwnPropertyDescriptors(local.factory);
222
+ const cacheKeys = Object.getOwnPropertySymbols(local.factory);
223
+ try {
224
+ assert.equal(cacheKeys.length, 2);
225
+ for (const key of cacheKeys) {
226
+ Object.defineProperty(local.factory, key, { configurable: true, get: probe.read });
227
+ }
228
+ Object.defineProperty(local.factory, 'app', { configurable: true, get: probe.read });
229
+ process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = 'true';
230
+ assert.equal(local.factory.get(), getSuppressedDiagnosticLogger());
231
+ assert.equal(local.factory.get(probe.value as any), getSuppressedDiagnosticLogger());
232
+ Object.defineProperty(local.factory, 'get', { configurable: true, get: probe.read });
233
+ assert.equal(local.factory.child(), getSuppressedDiagnosticLogger());
234
+ assert.equal(
235
+ local.factory.child(probe.value as any, probe.value as any),
236
+ getSuppressedDiagnosticLogger(),
237
+ );
238
+ assert.equal(local.creations(), 1);
239
+ assert.equal(probe.reads(), 0);
240
+ assert.equal(local.sink.count, 0);
241
+ } finally {
242
+ delete (local.factory as any).get;
243
+ delete (local.factory as any).app;
244
+ Object.defineProperties(local.factory, descriptors);
245
+ await local.close();
246
+ }
247
+ });
248
+
249
+ test('strict logger skips real configuration factories', { concurrency: false }, async () => {
250
+ const factory = new AppLogger();
251
+ const probe = throwingProbe();
252
+ let configurations = 0;
253
+ Object.defineProperty(factory, 'app', {
254
+ value: {
255
+ config: {
256
+ logger: {
257
+ base() {
258
+ configurations++;
259
+ return probe.read();
260
+ },
261
+ clients: {
262
+ default() {
263
+ configurations++;
264
+ return probe.read();
265
+ },
266
+ },
267
+ get baseDir() {
268
+ return probe.read();
269
+ },
270
+ get rotate() {
271
+ return probe.read();
272
+ },
273
+ },
274
+ },
275
+ },
276
+ });
277
+ try {
278
+ process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = 'true';
279
+ assert.equal(factory.get(), getSuppressedDiagnosticLogger());
280
+ assert.equal(factory.child('synthetic' as any), getSuppressedDiagnosticLogger());
281
+ assert.equal(configurations, 0);
282
+ assert.equal(probe.reads(), 0);
283
+ } finally {
284
+ await factory.dispose();
285
+ }
286
+ });
287
+
288
+ test('strict file transport fails before any input access', { concurrency: false }, () => {
289
+ const factory = new AppLogger();
290
+ const probe = throwingProbe();
291
+ const args = [probe.value, probe.value, probe.value] as unknown as Parameters<
292
+ AppLogger['createTransportFile']
293
+ >;
294
+ Object.defineProperty(factory, 'app', { get: probe.read });
295
+ process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = 'true';
296
+ assert.throws(() => factory.createTransportFile(...args), {
297
+ message: 'Strict test file logger transport is disabled',
298
+ });
299
+ runWithRequestBoundary(createRequestBoundary(), () => {
300
+ assert.throws(() => factory.createTransportFile(...args), {
301
+ message: 'Strict test file logger transport is disabled',
302
+ });
303
+ });
304
+ assert.equal(probe.reads(), 0);
305
+ });
306
+
307
+ test('invalid strict flags never fall back to loggers', { concurrency: false }, async () => {
308
+ const local = fixture();
309
+ const probe = throwingProbe();
310
+ Object.defineProperty(local.factory, 'app', { get: probe.read });
311
+ const args = [probe.value, probe.value, probe.value] as unknown as Parameters<
312
+ AppLogger['createTransportFile']
313
+ >;
314
+ try {
315
+ const env: NodeJS.ProcessEnv = { NODE_ENV: 'test', META_FLAVOR: 'normal', META_MODE: 'test' };
316
+ assert.equal(requiresFreshTestResources(), false);
317
+ assert.equal(requiresFreshTestResources(env), false);
318
+ assert.equal(
319
+ requiresFreshTestResources({ ...env, [TEST_REQUIRE_FRESH_RESOURCES_ENV]: 'true' }),
320
+ true,
321
+ );
322
+ for (const flag of ['', 'false', 'TRUE', 'synthetic-invalid-flag']) {
323
+ process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV] = flag;
324
+ const expected = { message: 'Invalid strict test resource freshness flag' };
325
+ assert.throws(() => requiresFreshTestResources(), expected);
326
+ assert.throws(
327
+ () => requiresFreshTestResources({ ...env, [TEST_REQUIRE_FRESH_RESOURCES_ENV]: flag }),
328
+ expected,
329
+ );
330
+ const calls = [
331
+ () => local.factory.get(),
332
+ () => local.factory.child('synthetic' as any),
333
+ () => local.factory.createTransportFile(...args),
334
+ ];
335
+ for (const call of calls) {
336
+ assert.throws(call, expected);
337
+ runWithRequestBoundary(createRequestBoundary(), () => assert.throws(call, expected));
338
+ }
339
+ }
340
+ assert.equal(probe.reads(), 0);
341
+ assert.equal(local.creations(), 1);
342
+ assert.equal(local.sink.count, 0);
343
+ } finally {
344
+ await local.close();
345
+ }
346
+ });
@@ -0,0 +1,138 @@
1
+ import type * as Winston from 'winston';
2
+
3
+ import { captureRequestBoundary, getRequestBoundary } from '../requestBoundary.ts';
4
+
5
+ const loggingMethods = new Set([
6
+ 'log',
7
+ 'error',
8
+ 'warn',
9
+ 'info',
10
+ 'http',
11
+ 'verbose',
12
+ 'debug',
13
+ 'silly',
14
+ ]);
15
+
16
+ function completeWrite(args: unknown[]) {
17
+ const callback = typeof args[1] === 'function' ? args[1] : args[2];
18
+ if (typeof callback === 'function') {
19
+ try {
20
+ callback();
21
+ } catch {
22
+ captureRequestBoundary()?.reportFailure('diagnostic_failed');
23
+ }
24
+ }
25
+ return true;
26
+ }
27
+
28
+ const suppressedLogger = createFacade();
29
+
30
+ export function getSuppressedDiagnosticLogger(): Winston.Logger {
31
+ return suppressedLogger;
32
+ }
33
+
34
+ export function createDiagnosticLogger(raw: Winston.Logger): Winston.Logger {
35
+ if (getRequestBoundary()) return getSuppressedDiagnosticLogger();
36
+ return createFacade(raw);
37
+ }
38
+
39
+ function createFacade(raw?: Winston.Logger): Winston.Logger {
40
+ const methods = new Map<PropertyKey, (...args: any[]) => any>();
41
+ const handlers = new WeakMap<object, Winston.ExceptionHandler | Winston.RejectionHandler>();
42
+ const inertProfiler = Object.freeze({
43
+ get logger() {
44
+ return getSuppressedDiagnosticLogger();
45
+ },
46
+ done: () => getSuppressedDiagnosticLogger(),
47
+ });
48
+ const facade = new Proxy({} as Winston.Logger, {
49
+ get(_target, key) {
50
+ if (
51
+ loggingMethods.has(key as string) ||
52
+ ['write', 'child', 'startTimer', 'profile'].includes(key as string)
53
+ ) {
54
+ if (!methods.has(key)) {
55
+ methods.set(key, (...args: any[]) => {
56
+ if (!raw || getRequestBoundary()) {
57
+ if (key === 'write') return completeWrite(args);
58
+ if (key === 'child') return getSuppressedDiagnosticLogger();
59
+ if (key === 'startTimer') return inertProfiler;
60
+ return facade;
61
+ }
62
+ const result = Reflect.apply(Reflect.get(raw, key, raw), raw, args);
63
+ if (key === 'child') return createFacade(result as Winston.Logger);
64
+ if (key === 'startTimer') return wrapProfiler(result as Winston.Profiler, facade);
65
+ return result === raw ? facade : result;
66
+ });
67
+ }
68
+ return methods.get(key);
69
+ }
70
+ if (!raw) return undefined;
71
+ const value = Reflect.get(raw, key, raw);
72
+ if ((key === 'exceptions' || key === 'rejections') && value) {
73
+ if (!handlers.has(value)) handlers.set(value, wrapHandler(value, raw, facade));
74
+ return handlers.get(value);
75
+ }
76
+ if (typeof value !== 'function') return value;
77
+ if (!methods.has(key)) {
78
+ methods.set(key, (...args: any[]) => {
79
+ const result = Reflect.apply(value, raw, args);
80
+ return result === raw ? facade : result;
81
+ });
82
+ }
83
+ return methods.get(key);
84
+ },
85
+ set(_target, key, value) {
86
+ return raw ? Reflect.set(raw, key, value, raw) : true;
87
+ },
88
+ });
89
+ return facade;
90
+ }
91
+
92
+ function wrapHandler<T extends Winston.ExceptionHandler | Winston.RejectionHandler>(
93
+ raw: T,
94
+ rawLogger: Winston.Logger,
95
+ logger: Winston.Logger,
96
+ ): T {
97
+ const methods = new Map<PropertyKey, (...args: any[]) => any>();
98
+ const handler = new Proxy({} as T, {
99
+ get(_target, key) {
100
+ if (key === 'logger') return logger;
101
+ const value = Reflect.get(raw, key, raw);
102
+ if (typeof value !== 'function') return value;
103
+ if (!methods.has(key)) {
104
+ methods.set(key, (...args: any[]) => {
105
+ const result = Reflect.apply(value, raw, args);
106
+ if (result === raw) return handler;
107
+ return result === rawLogger ? logger : result;
108
+ });
109
+ }
110
+ return methods.get(key);
111
+ },
112
+ set(_target, key, value) {
113
+ if (key === 'logger') return false;
114
+ return Reflect.set(raw, key, value, raw);
115
+ },
116
+ });
117
+ return handler;
118
+ }
119
+
120
+ function wrapProfiler(raw: Winston.Profiler, logger: Winston.Logger): Winston.Profiler {
121
+ return new Proxy({} as Winston.Profiler, {
122
+ get(_target, key) {
123
+ if (key === 'logger') return logger;
124
+ if (key === 'done') {
125
+ return (...args: any[]) => {
126
+ if (getRequestBoundary()) return logger;
127
+ const result = Reflect.apply(raw.done, raw, args);
128
+ return result === raw.logger ? logger : result;
129
+ };
130
+ }
131
+ return Reflect.get(raw, key, raw);
132
+ },
133
+ set(_target, key, value) {
134
+ if (key === 'logger') return false;
135
+ return Reflect.set(raw, key, value, raw);
136
+ },
137
+ });
138
+ }
@@ -1,2 +1,3 @@
1
1
  export * from './appLogger.ts';
2
+ export { createDiagnosticLogger, getSuppressedDiagnosticLogger } from './diagnosticLogger.ts';
2
3
  export * from './utils.ts';
@@ -11,10 +11,18 @@ import type {
11
11
 
12
12
  import { cast } from '../../../types/utils/cast.ts';
13
13
  import { useApp } from '../../framework/useApp.ts';
14
+ import { getRequestBoundary } from '../requestBoundary.ts';
14
15
 
15
16
  const SymbolLoggerMessage = Symbol('SymbolLoggerMessage');
16
17
 
17
- export const formatLoggerAxiosError = Winston.format((einfo, { stack, cause }: any) => {
18
+ export const formatLoggerBoundary = Winston.format(info => {
19
+ if (getRequestBoundary()) return false;
20
+ return info;
21
+ });
22
+
23
+ export const formatLoggerAxiosError = Winston.format((einfo, opts: any) => {
24
+ if (getRequestBoundary()) return false;
25
+ const { stack, cause } = opts;
18
26
  if (
19
27
  (einfo instanceof Error && einfo.constructor.name.includes('AxiosError')) ||
20
28
  einfo.name === 'AxiosError' ||
@@ -39,10 +47,16 @@ export const formatLoggerAxiosError = Winston.format((einfo, { stack, cause }: a
39
47
  });
40
48
 
41
49
  export const formatLoggerErrors = (opts: ILoggerFormatOpts) => {
42
- return Winston.format.combine(formatLoggerAxiosError(opts), Winston.format.errors(opts));
50
+ if (getRequestBoundary()) return formatLoggerBoundary();
51
+ return Winston.format.combine(
52
+ formatLoggerBoundary(),
53
+ formatLoggerAxiosError(opts),
54
+ Winston.format.errors(opts),
55
+ );
43
56
  };
44
57
 
45
58
  export const formatLoggerCtx = Winston.format((info, _opts: any) => {
59
+ if (getRequestBoundary()) return false;
46
60
  const app = useApp();
47
61
  if (!app.ctx) return info;
48
62
  if (app.ctx.method) info.method = app.ctx.method;
@@ -60,12 +74,14 @@ export const formatLoggerCtx = Winston.format((info, _opts: any) => {
60
74
  });
61
75
 
62
76
  export const formatLoggerDummy = Winston.format(info => {
77
+ if (getRequestBoundary()) return false;
63
78
  const app = useApp();
64
79
  if (app.meta.env.LOGGER_DUMMY === 'true') return false;
65
80
  return info;
66
81
  });
67
82
 
68
83
  export const formatLoggerFilter = Winston.format(((info: any, opts: ILoggerFormatFilterOpts) => {
84
+ if (getRequestBoundary()) return false;
69
85
  // check client
70
86
  const child = typeof opts.child === 'function' ? opts.child() : opts.child;
71
87
  if (child && !child.includes(info.name)) {
@@ -90,8 +106,11 @@ export const formatLoggerFilter = Winston.format(((info: any, opts: ILoggerForma
90
106
  }) as any);
91
107
 
92
108
  export const formatLoggerConsole = (clientInfo: ILoggerOptionsClientInfo) => {
93
- return Winston.format.printf(
94
- ({ timestamp, level, stack, message, name, beanFullName, durationMs, ...meta }) => {
109
+ return Winston.format.combine(
110
+ formatLoggerBoundary(),
111
+ Winston.format.printf(info => {
112
+ if (getRequestBoundary()) return '';
113
+ const { timestamp, level, stack, message, name, beanFullName, durationMs, ...meta } = info;
95
114
  const textClientName =
96
115
  clientInfo.clientName === 'default' ? '' : ` ${chalk.cyan(`[${clientInfo.clientName}]`)}`;
97
116
  const textName = name ? ` ${chalk.cyan(`[${name}]`)}` : '';
@@ -101,11 +120,12 @@ export const formatLoggerConsole = (clientInfo: ILoggerOptionsClientInfo) => {
101
120
  const textDurationMs = durationMs !== undefined ? ` ${chalk.cyan(`+${durationMs}ms`)}` : '';
102
121
  const textStack = stack ? `\n${stack}` : '';
103
122
  return `${timestamp} ${level}${textClientName}${textName}${textBeanFullName}${textMeta}${textMessage}${textDurationMs}${textStack}`;
104
- },
123
+ }),
105
124
  );
106
125
  };
107
126
 
108
127
  function __formatLoggerFilterCheckInfo(info) {
128
+ if (getRequestBoundary()) return false;
109
129
  if (typeof info.message === 'function') {
110
130
  if (info.message[SymbolLoggerMessage] === undefined) {
111
131
  info.message[SymbolLoggerMessage] = info.message();
@@ -0,0 +1,89 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+
3
+ export type RequestBoundaryFailure =
4
+ | 'observer_failed'
5
+ | 'diagnostic_failed'
6
+ | 'late_callback_failed';
7
+ export type VonaScopeOutcome = 'not_created' | 'disposed' | 'dispose_failed';
8
+ export type VonaScopeOutcomeObserver = (outcome: VonaScopeOutcome) => void;
9
+
10
+ declare const requestBoundaryBrand: unique symbol;
11
+
12
+ export interface RequestBoundary {
13
+ readonly [requestBoundaryBrand]: true;
14
+ }
15
+
16
+ export interface RequestBoundaryCapture {
17
+ readonly diagnostics: 'suppress';
18
+ reportFailure(failure: RequestBoundaryFailure): void;
19
+ snapshot(): Readonly<{ portFault: boolean }>;
20
+ }
21
+
22
+ const boundaries = new WeakMap<RequestBoundary, RequestBoundaryCapture>();
23
+ const boundaryStorage = new AsyncLocalStorage<RequestBoundary>();
24
+
25
+ export function createRequestBoundary(options?: {
26
+ readonly onFailure?: (failure: RequestBoundaryFailure) => void;
27
+ }): RequestBoundary {
28
+ const onFailure = options?.onFailure;
29
+ const boundary = Object.freeze({}) as RequestBoundary;
30
+ let portFault = false;
31
+ let reporting = false;
32
+ const reported = new Set<RequestBoundaryFailure>();
33
+ const capture: RequestBoundaryCapture = Object.freeze({
34
+ diagnostics: 'suppress',
35
+ reportFailure(failure: RequestBoundaryFailure) {
36
+ if (
37
+ failure !== 'observer_failed' &&
38
+ failure !== 'diagnostic_failed' &&
39
+ failure !== 'late_callback_failed'
40
+ ) {
41
+ portFault = true;
42
+ return;
43
+ }
44
+ portFault = true;
45
+ if (reported.has(failure)) return;
46
+ reported.add(failure);
47
+ if (reporting || !onFailure) return;
48
+ reporting = true;
49
+ try {
50
+ const result: unknown = onFailure(failure);
51
+ if (result && (typeof result === 'object' || typeof result === 'function')) {
52
+ const then = (result as PromiseLike<unknown>).then;
53
+ if (typeof then === 'function') {
54
+ portFault = true;
55
+ void new Promise<unknown>((resolve, reject) => {
56
+ then.call(result, resolve, reject);
57
+ }).catch(() => {});
58
+ }
59
+ }
60
+ } catch {
61
+ portFault = true;
62
+ } finally {
63
+ reporting = false;
64
+ }
65
+ },
66
+ snapshot() {
67
+ return Object.freeze({ portFault });
68
+ },
69
+ });
70
+ boundaries.set(boundary, capture);
71
+ return boundary;
72
+ }
73
+
74
+ export function runWithRequestBoundary<T>(boundary: RequestBoundary, continuation: () => T): T {
75
+ if (!boundaries.has(boundary)) throw new Error('Invalid request boundary');
76
+ const current = boundaryStorage.getStore();
77
+ if (current && current !== boundary) throw new Error('Request boundary cannot be replaced');
78
+ if (current === boundary) return continuation();
79
+ return boundaryStorage.run(boundary, continuation);
80
+ }
81
+
82
+ export function getRequestBoundary(): RequestBoundary | undefined {
83
+ return boundaryStorage.getStore();
84
+ }
85
+
86
+ export function captureRequestBoundary(): RequestBoundaryCapture | undefined {
87
+ const boundary = boundaryStorage.getStore();
88
+ return boundary ? boundaries.get(boundary) : undefined;
89
+ }