vona-core 5.1.47 → 5.1.49
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/dist/index.js +439 -27
- package/dist/index.js.map +1 -1
- package/dist/lib/bean/beanBase.d.ts.map +1 -1
- package/dist/lib/core/asyncLocalStorage.d.ts +6 -0
- package/dist/lib/core/asyncLocalStorage.d.ts.map +1 -1
- package/dist/lib/core/index.d.ts +3 -0
- package/dist/lib/core/index.d.ts.map +1 -1
- package/dist/lib/core/logger/appLogger.d.ts +2 -0
- package/dist/lib/core/logger/appLogger.d.ts.map +1 -1
- package/dist/lib/core/logger/diagnosticLogger.d.ts +4 -0
- package/dist/lib/core/logger/diagnosticLogger.d.ts.map +1 -0
- package/dist/lib/core/logger/diagnosticLogger.test.d.ts +2 -0
- package/dist/lib/core/logger/diagnosticLogger.test.d.ts.map +1 -0
- package/dist/lib/core/logger/index.d.ts +1 -0
- package/dist/lib/core/logger/index.d.ts.map +1 -1
- package/dist/lib/core/logger/utils.d.ts +1 -0
- package/dist/lib/core/logger/utils.d.ts.map +1 -1
- package/dist/lib/core/requestBoundary.d.ts +22 -0
- package/dist/lib/core/requestBoundary.d.ts.map +1 -0
- package/dist/lib/core/testFreshness.d.ts +20 -0
- package/dist/lib/core/testFreshness.d.ts.map +1 -0
- package/dist/lib/core/testFreshness.test.d.ts +2 -0
- package/dist/lib/core/testFreshness.test.d.ts.map +1 -0
- package/dist/lib/core/testStrictMode.d.ts +3 -0
- package/dist/lib/core/testStrictMode.d.ts.map +1 -0
- package/dist/lib/framework/start.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/lib/bean/beanBase.ts +4 -0
- package/src/lib/core/asyncLocalStorage.ts +84 -12
- package/src/lib/core/index.ts +14 -0
- package/src/lib/core/logger/appLogger.ts +19 -3
- package/src/lib/core/logger/diagnosticLogger.test.ts +346 -0
- package/src/lib/core/logger/diagnosticLogger.ts +138 -0
- package/src/lib/core/logger/index.ts +1 -0
- package/src/lib/core/logger/utils.ts +25 -5
- package/src/lib/core/requestBoundary.ts +89 -0
- package/src/lib/core/testFreshness.test.ts +379 -0
- package/src/lib/core/testFreshness.ts +232 -0
- package/src/lib/core/testStrictMode.ts +9 -0
- package/src/lib/framework/start.test.ts +4 -3
- package/src/lib/framework/start.ts +2 -0
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import '@cabloy/json5';
|
|
2
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
3
|
import { combineApiPathControllerAndActionRaw, combineApiPathControllerAndAction, combineApiPath, combineResourceName, forEach, forEachSync, sleep, isUndefined, isClass, isNilOrEmptyString, evaluateExpressions, catchErrorSync, isEmptyObject, catchError, hashkey } from '@cabloy/utils';
|
|
3
4
|
import { toLowerCaseFirstChar, splitWords } from '@cabloy/word-utils';
|
|
4
5
|
import path from 'node:path';
|
|
@@ -12,7 +13,6 @@ import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
|
12
13
|
import * as uuid from 'uuid';
|
|
13
14
|
import { getPlatformArch as getPlatformArch$1 } from 'vona';
|
|
14
15
|
import os from 'node:os';
|
|
15
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
16
16
|
import { isDeepStrictEqual } from 'node:util';
|
|
17
17
|
import { setLocaleErrors, setLocaleAdapter, translateError } from '@cabloy/zod-errors-custom';
|
|
18
18
|
import { ZodMetadata } from '@cabloy/zod-openapi';
|
|
@@ -27,7 +27,7 @@ import * as Winston from 'winston';
|
|
|
27
27
|
import DailyRotateFile from 'winston-daily-rotate-file';
|
|
28
28
|
import { MESSAGE, LEVEL } from 'triple-beam';
|
|
29
29
|
import { createServer } from 'node:http';
|
|
30
|
-
import { mkdir, open, rename, unlink, lstat } from 'node:fs/promises';
|
|
30
|
+
import { mkdir, open, rename, unlink, lstat, readdir } from 'node:fs/promises';
|
|
31
31
|
import { execFileSync, execFile } from 'node:child_process';
|
|
32
32
|
import cluster from 'node:cluster';
|
|
33
33
|
import React from 'react';
|
|
@@ -36,6 +36,179 @@ import { z } from 'zod';
|
|
|
36
36
|
import * as Retry from 'retry';
|
|
37
37
|
export { Retry };
|
|
38
38
|
|
|
39
|
+
const boundaries = new WeakMap();
|
|
40
|
+
const boundaryStorage = new AsyncLocalStorage();
|
|
41
|
+
function createRequestBoundary(options) {
|
|
42
|
+
const onFailure = options?.onFailure;
|
|
43
|
+
const boundary = Object.freeze({});
|
|
44
|
+
let portFault = false;
|
|
45
|
+
let reporting = false;
|
|
46
|
+
const reported = new Set();
|
|
47
|
+
const capture = Object.freeze({
|
|
48
|
+
diagnostics: 'suppress',
|
|
49
|
+
reportFailure(failure) {
|
|
50
|
+
if (failure !== 'observer_failed' && failure !== 'diagnostic_failed' && failure !== 'late_callback_failed') {
|
|
51
|
+
portFault = true;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
portFault = true;
|
|
55
|
+
if (reported.has(failure)) return;
|
|
56
|
+
reported.add(failure);
|
|
57
|
+
if (reporting || !onFailure) return;
|
|
58
|
+
reporting = true;
|
|
59
|
+
try {
|
|
60
|
+
const result = onFailure(failure);
|
|
61
|
+
if (result && (typeof result === 'object' || typeof result === 'function')) {
|
|
62
|
+
const then = result.then;
|
|
63
|
+
if (typeof then === 'function') {
|
|
64
|
+
portFault = true;
|
|
65
|
+
void new Promise((resolve, reject) => {
|
|
66
|
+
then.call(result, resolve, reject);
|
|
67
|
+
}).catch(() => {});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
portFault = true;
|
|
72
|
+
} finally {
|
|
73
|
+
reporting = false;
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
snapshot() {
|
|
77
|
+
return Object.freeze({
|
|
78
|
+
portFault
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
boundaries.set(boundary, capture);
|
|
83
|
+
return boundary;
|
|
84
|
+
}
|
|
85
|
+
function runWithRequestBoundary(boundary, continuation) {
|
|
86
|
+
if (!boundaries.has(boundary)) throw new Error('Invalid request boundary');
|
|
87
|
+
const current = boundaryStorage.getStore();
|
|
88
|
+
if (current && current !== boundary) throw new Error('Request boundary cannot be replaced');
|
|
89
|
+
if (current === boundary) return continuation();
|
|
90
|
+
return boundaryStorage.run(boundary, continuation);
|
|
91
|
+
}
|
|
92
|
+
function getRequestBoundary() {
|
|
93
|
+
return boundaryStorage.getStore();
|
|
94
|
+
}
|
|
95
|
+
function captureRequestBoundary() {
|
|
96
|
+
const boundary = boundaryStorage.getStore();
|
|
97
|
+
return boundary ? boundaries.get(boundary) : undefined;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const loggingMethods = new Set(['log', 'error', 'warn', 'info', 'http', 'verbose', 'debug', 'silly']);
|
|
101
|
+
function completeWrite(args) {
|
|
102
|
+
const callback = typeof args[1] === 'function' ? args[1] : args[2];
|
|
103
|
+
if (typeof callback === 'function') {
|
|
104
|
+
try {
|
|
105
|
+
callback();
|
|
106
|
+
} catch {
|
|
107
|
+
captureRequestBoundary()?.reportFailure('diagnostic_failed');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
const suppressedLogger = createFacade();
|
|
113
|
+
function getSuppressedDiagnosticLogger() {
|
|
114
|
+
return suppressedLogger;
|
|
115
|
+
}
|
|
116
|
+
function createDiagnosticLogger(raw) {
|
|
117
|
+
if (getRequestBoundary()) return getSuppressedDiagnosticLogger();
|
|
118
|
+
return createFacade(raw);
|
|
119
|
+
}
|
|
120
|
+
function createFacade(raw) {
|
|
121
|
+
const methods = new Map();
|
|
122
|
+
const handlers = new WeakMap();
|
|
123
|
+
const inertProfiler = Object.freeze({
|
|
124
|
+
get logger() {
|
|
125
|
+
return getSuppressedDiagnosticLogger();
|
|
126
|
+
},
|
|
127
|
+
done: () => getSuppressedDiagnosticLogger()
|
|
128
|
+
});
|
|
129
|
+
const facade = new Proxy({}, {
|
|
130
|
+
get(_target, key) {
|
|
131
|
+
if (loggingMethods.has(key) || ['write', 'child', 'startTimer', 'profile'].includes(key)) {
|
|
132
|
+
if (!methods.has(key)) {
|
|
133
|
+
methods.set(key, (...args) => {
|
|
134
|
+
if (!raw || getRequestBoundary()) {
|
|
135
|
+
if (key === 'write') return completeWrite(args);
|
|
136
|
+
if (key === 'child') return getSuppressedDiagnosticLogger();
|
|
137
|
+
if (key === 'startTimer') return inertProfiler;
|
|
138
|
+
return facade;
|
|
139
|
+
}
|
|
140
|
+
const result = Reflect.apply(Reflect.get(raw, key, raw), raw, args);
|
|
141
|
+
if (key === 'child') return createFacade(result);
|
|
142
|
+
if (key === 'startTimer') return wrapProfiler(result, facade);
|
|
143
|
+
return result === raw ? facade : result;
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return methods.get(key);
|
|
147
|
+
}
|
|
148
|
+
if (!raw) return undefined;
|
|
149
|
+
const value = Reflect.get(raw, key, raw);
|
|
150
|
+
if ((key === 'exceptions' || key === 'rejections') && value) {
|
|
151
|
+
if (!handlers.has(value)) handlers.set(value, wrapHandler(value, raw, facade));
|
|
152
|
+
return handlers.get(value);
|
|
153
|
+
}
|
|
154
|
+
if (typeof value !== 'function') return value;
|
|
155
|
+
if (!methods.has(key)) {
|
|
156
|
+
methods.set(key, (...args) => {
|
|
157
|
+
const result = Reflect.apply(value, raw, args);
|
|
158
|
+
return result === raw ? facade : result;
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
return methods.get(key);
|
|
162
|
+
},
|
|
163
|
+
set(_target, key, value) {
|
|
164
|
+
return raw ? Reflect.set(raw, key, value, raw) : true;
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
return facade;
|
|
168
|
+
}
|
|
169
|
+
function wrapHandler(raw, rawLogger, logger) {
|
|
170
|
+
const methods = new Map();
|
|
171
|
+
const handler = new Proxy({}, {
|
|
172
|
+
get(_target, key) {
|
|
173
|
+
if (key === 'logger') return logger;
|
|
174
|
+
const value = Reflect.get(raw, key, raw);
|
|
175
|
+
if (typeof value !== 'function') return value;
|
|
176
|
+
if (!methods.has(key)) {
|
|
177
|
+
methods.set(key, (...args) => {
|
|
178
|
+
const result = Reflect.apply(value, raw, args);
|
|
179
|
+
if (result === raw) return handler;
|
|
180
|
+
return result === rawLogger ? logger : result;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
return methods.get(key);
|
|
184
|
+
},
|
|
185
|
+
set(_target, key, value) {
|
|
186
|
+
if (key === 'logger') return false;
|
|
187
|
+
return Reflect.set(raw, key, value, raw);
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
return handler;
|
|
191
|
+
}
|
|
192
|
+
function wrapProfiler(raw, logger) {
|
|
193
|
+
return new Proxy({}, {
|
|
194
|
+
get(_target, key) {
|
|
195
|
+
if (key === 'logger') return logger;
|
|
196
|
+
if (key === 'done') {
|
|
197
|
+
return (...args) => {
|
|
198
|
+
if (getRequestBoundary()) return logger;
|
|
199
|
+
const result = Reflect.apply(raw.done, raw, args);
|
|
200
|
+
return result === raw.logger ? logger : result;
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
return Reflect.get(raw, key, raw);
|
|
204
|
+
},
|
|
205
|
+
set(_target, key, value) {
|
|
206
|
+
if (key === 'logger') return false;
|
|
207
|
+
return Reflect.set(raw, key, value, raw);
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
39
212
|
function cast(source) {
|
|
40
213
|
return source;
|
|
41
214
|
}
|
|
@@ -1185,6 +1358,7 @@ class BeanBase extends BeanBaseSimple {
|
|
|
1185
1358
|
return this.$loggerClient('default');
|
|
1186
1359
|
}
|
|
1187
1360
|
$loggerClient(clientName = 'default') {
|
|
1361
|
+
if (getRequestBoundary()) return getSuppressedDiagnosticLogger();
|
|
1188
1362
|
if (!this[SymbolLogger][clientName]) {
|
|
1189
1363
|
this[SymbolLogger][clientName] = this.app.meta.logger.get(clientName).child({
|
|
1190
1364
|
beanFullName: this.$beanFullName
|
|
@@ -1193,6 +1367,7 @@ class BeanBase extends BeanBaseSimple {
|
|
|
1193
1367
|
return this[SymbolLogger][clientName];
|
|
1194
1368
|
}
|
|
1195
1369
|
$loggerChild(childName, clientName = 'default') {
|
|
1370
|
+
if (getRequestBoundary()) return getSuppressedDiagnosticLogger();
|
|
1196
1371
|
if (!this[SymbolLoggerChildren][clientName]) this[SymbolLoggerChildren][clientName] = {};
|
|
1197
1372
|
if (!this[SymbolLoggerChildren][clientName][childName]) {
|
|
1198
1373
|
this[SymbolLoggerChildren][clientName][childName] = this.app.meta.logger.get(clientName).child({
|
|
@@ -2456,21 +2631,72 @@ class VonaAsyncLocalStorage extends AsyncLocalStorage {
|
|
|
2456
2631
|
constructor(app) {
|
|
2457
2632
|
super();
|
|
2458
2633
|
this.app = void 0;
|
|
2634
|
+
this.observedRuns = new WeakMap();
|
|
2459
2635
|
this.app = app;
|
|
2460
2636
|
}
|
|
2461
2637
|
async run(store, callback, ...args) {
|
|
2462
2638
|
if (store === this.app.currentContext) {
|
|
2463
2639
|
return await callback(...args);
|
|
2464
2640
|
}
|
|
2465
|
-
|
|
2641
|
+
if (this.observedRuns.has(store)) throw new Error('Context scope already owned');
|
|
2642
|
+
return this._runOwned(store, () => callback(...args));
|
|
2643
|
+
}
|
|
2644
|
+
async runObserved(store, callback, observer) {
|
|
2645
|
+
if (store === this.app.currentContext || this.observedRuns.has(store)) {
|
|
2646
|
+
throw new Error('Context scope already owned');
|
|
2647
|
+
}
|
|
2648
|
+
const frame = {
|
|
2649
|
+
settled: false
|
|
2650
|
+
};
|
|
2651
|
+
this.observedRuns.set(store, frame);
|
|
2652
|
+
try {
|
|
2653
|
+
return await this._runOwned(store, callback, observer);
|
|
2654
|
+
} finally {
|
|
2655
|
+
frame.settled = true;
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
_runOwned(store, callback, observer) {
|
|
2659
|
+
const capture = captureRequestBoundary();
|
|
2660
|
+
return super.run(store, async () => {
|
|
2466
2661
|
try {
|
|
2467
2662
|
this.app.meta.ctxCounter.increment();
|
|
2468
|
-
return await callback(
|
|
2663
|
+
return await callback();
|
|
2469
2664
|
} finally {
|
|
2470
|
-
await
|
|
2471
|
-
this.app.meta.ctxCounter.decrement();
|
|
2665
|
+
await this._disposeOwned(store, observer, capture);
|
|
2472
2666
|
}
|
|
2473
|
-
}
|
|
2667
|
+
});
|
|
2668
|
+
}
|
|
2669
|
+
async _disposeOwned(store, observer, capture) {
|
|
2670
|
+
try {
|
|
2671
|
+
await store.bean.dispose();
|
|
2672
|
+
} catch (error) {
|
|
2673
|
+
this._notifyScopeOutcome(observer, 'dispose_failed', capture);
|
|
2674
|
+
throw error;
|
|
2675
|
+
}
|
|
2676
|
+
try {
|
|
2677
|
+
this.app.meta.ctxCounter.decrement();
|
|
2678
|
+
} catch (error) {
|
|
2679
|
+
capture?.reportFailure('diagnostic_failed');
|
|
2680
|
+
throw error;
|
|
2681
|
+
}
|
|
2682
|
+
this._notifyScopeOutcome(observer, 'disposed', capture);
|
|
2683
|
+
}
|
|
2684
|
+
_notifyScopeOutcome(observer, outcome, capture) {
|
|
2685
|
+
if (!observer) return;
|
|
2686
|
+
try {
|
|
2687
|
+
const result = observer(outcome);
|
|
2688
|
+
if (result && (typeof result === 'object' || typeof result === 'function')) {
|
|
2689
|
+
const then = result.then;
|
|
2690
|
+
if (typeof then === 'function') {
|
|
2691
|
+
capture?.reportFailure('observer_failed');
|
|
2692
|
+
void new Promise((resolve, reject) => {
|
|
2693
|
+
then.call(result, resolve, reject);
|
|
2694
|
+
}).catch(() => {});
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
} catch {
|
|
2698
|
+
capture?.reportFailure('observer_failed');
|
|
2699
|
+
}
|
|
2474
2700
|
}
|
|
2475
2701
|
}
|
|
2476
2702
|
|
|
@@ -2646,26 +2872,45 @@ class AppHmr extends BeanSimple {
|
|
|
2646
2872
|
}
|
|
2647
2873
|
}
|
|
2648
2874
|
|
|
2875
|
+
const TEST_REQUIRE_FRESH_RESOURCES_ENV = 'VONA_TEST_REQUIRE_FRESH_RESOURCES';
|
|
2876
|
+
function requiresFreshTestResources(env = process.env) {
|
|
2877
|
+
const value = env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
|
|
2878
|
+
if (value !== undefined && value !== 'true') {
|
|
2879
|
+
throw new Error('Invalid strict test resource freshness flag');
|
|
2880
|
+
}
|
|
2881
|
+
return value === 'true';
|
|
2882
|
+
}
|
|
2883
|
+
|
|
2649
2884
|
const SymbolLoggerInstances = Symbol('SymbolLoggerInstances');
|
|
2885
|
+
const SymbolRawLoggerInstances = Symbol('SymbolRawLoggerInstances');
|
|
2650
2886
|
class AppLogger extends BeanSimple {
|
|
2651
2887
|
constructor(...args) {
|
|
2652
2888
|
super(...args);
|
|
2653
2889
|
this[SymbolLoggerInstances] = {};
|
|
2890
|
+
this[SymbolRawLoggerInstances] = {};
|
|
2654
2891
|
}
|
|
2655
2892
|
async dispose() {
|
|
2656
|
-
for (const key in this[
|
|
2657
|
-
const logger = this[
|
|
2893
|
+
for (const key in this[SymbolRawLoggerInstances]) {
|
|
2894
|
+
const logger = this[SymbolRawLoggerInstances][key];
|
|
2658
2895
|
await _closeLogger(logger);
|
|
2659
2896
|
}
|
|
2660
2897
|
}
|
|
2661
2898
|
get(clientName) {
|
|
2899
|
+
if (requiresFreshTestResources() || getRequestBoundary()) {
|
|
2900
|
+
return getSuppressedDiagnosticLogger();
|
|
2901
|
+
}
|
|
2662
2902
|
clientName = clientName || 'default';
|
|
2663
2903
|
if (!this[SymbolLoggerInstances][clientName]) {
|
|
2664
|
-
|
|
2904
|
+
const raw = this._createClient(clientName);
|
|
2905
|
+
this[SymbolRawLoggerInstances][clientName] = raw;
|
|
2906
|
+
this[SymbolLoggerInstances][clientName] = createDiagnosticLogger(raw);
|
|
2665
2907
|
}
|
|
2666
2908
|
return this[SymbolLoggerInstances][clientName];
|
|
2667
2909
|
}
|
|
2668
2910
|
child(childName, clientName) {
|
|
2911
|
+
if (requiresFreshTestResources() || getRequestBoundary()) {
|
|
2912
|
+
return getSuppressedDiagnosticLogger();
|
|
2913
|
+
}
|
|
2669
2914
|
const logger = this.get(clientName);
|
|
2670
2915
|
if (!childName) return logger;
|
|
2671
2916
|
return logger.child({
|
|
@@ -2721,6 +2966,9 @@ class AppLogger extends BeanSimple {
|
|
|
2721
2966
|
}, Winston);
|
|
2722
2967
|
}
|
|
2723
2968
|
createTransportFile(fileName, clientInfo, options) {
|
|
2969
|
+
if (requiresFreshTestResources()) {
|
|
2970
|
+
throw new Error('Strict test file logger transport is disabled');
|
|
2971
|
+
}
|
|
2724
2972
|
const dirname = this.app.config.logger.baseDir;
|
|
2725
2973
|
if (!fse.existsSync(dirname)) {
|
|
2726
2974
|
const [_, err] = catchErrorSync(() => {
|
|
@@ -3081,10 +3329,16 @@ var localesDefault = {
|
|
|
3081
3329
|
};
|
|
3082
3330
|
|
|
3083
3331
|
const SymbolLoggerMessage = Symbol('SymbolLoggerMessage');
|
|
3084
|
-
const
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
})
|
|
3332
|
+
const formatLoggerBoundary = Winston.format(info => {
|
|
3333
|
+
if (getRequestBoundary()) return false;
|
|
3334
|
+
return info;
|
|
3335
|
+
});
|
|
3336
|
+
const formatLoggerAxiosError = Winston.format((einfo, opts) => {
|
|
3337
|
+
if (getRequestBoundary()) return false;
|
|
3338
|
+
const {
|
|
3339
|
+
stack,
|
|
3340
|
+
cause
|
|
3341
|
+
} = opts;
|
|
3088
3342
|
if (einfo instanceof Error && einfo.constructor.name.includes('AxiosError') || einfo.name === 'AxiosError' || einfo.isAxiosError) {
|
|
3089
3343
|
const info = Object.assign({}, einfo, {
|
|
3090
3344
|
level: einfo.level,
|
|
@@ -3104,9 +3358,11 @@ const formatLoggerAxiosError = Winston.format((einfo, {
|
|
|
3104
3358
|
return einfo;
|
|
3105
3359
|
});
|
|
3106
3360
|
const formatLoggerErrors = opts => {
|
|
3107
|
-
|
|
3361
|
+
if (getRequestBoundary()) return formatLoggerBoundary();
|
|
3362
|
+
return Winston.format.combine(formatLoggerBoundary(), formatLoggerAxiosError(opts), Winston.format.errors(opts));
|
|
3108
3363
|
};
|
|
3109
3364
|
const formatLoggerCtx = Winston.format((info, _opts) => {
|
|
3365
|
+
if (getRequestBoundary()) return false;
|
|
3110
3366
|
const app = useApp();
|
|
3111
3367
|
if (!app.ctx) return info;
|
|
3112
3368
|
if (app.ctx.method) info.method = app.ctx.method;
|
|
@@ -3123,11 +3379,13 @@ const formatLoggerCtx = Winston.format((info, _opts) => {
|
|
|
3123
3379
|
return info;
|
|
3124
3380
|
});
|
|
3125
3381
|
const formatLoggerDummy = Winston.format(info => {
|
|
3382
|
+
if (getRequestBoundary()) return false;
|
|
3126
3383
|
const app = useApp();
|
|
3127
3384
|
if (app.meta.env.LOGGER_DUMMY === 'true') return false;
|
|
3128
3385
|
return info;
|
|
3129
3386
|
});
|
|
3130
3387
|
const formatLoggerFilter = Winston.format((info, opts) => {
|
|
3388
|
+
if (getRequestBoundary()) return false;
|
|
3131
3389
|
// check client
|
|
3132
3390
|
const child = typeof opts.child === 'function' ? opts.child() : opts.child;
|
|
3133
3391
|
if (child && !child.includes(info.name)) {
|
|
@@ -3147,16 +3405,18 @@ const formatLoggerFilter = Winston.format((info, opts) => {
|
|
|
3147
3405
|
return false;
|
|
3148
3406
|
});
|
|
3149
3407
|
const formatLoggerConsole = clientInfo => {
|
|
3150
|
-
return Winston.format.printf(
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3408
|
+
return Winston.format.combine(formatLoggerBoundary(), Winston.format.printf(info => {
|
|
3409
|
+
if (getRequestBoundary()) return '';
|
|
3410
|
+
const {
|
|
3411
|
+
timestamp,
|
|
3412
|
+
level,
|
|
3413
|
+
stack,
|
|
3414
|
+
message,
|
|
3415
|
+
name,
|
|
3416
|
+
beanFullName,
|
|
3417
|
+
durationMs,
|
|
3418
|
+
...meta
|
|
3419
|
+
} = info;
|
|
3160
3420
|
const textClientName = clientInfo.clientName === 'default' ? '' : ` ${chalk.cyan(`[${clientInfo.clientName}]`)}`;
|
|
3161
3421
|
const textName = name ? ` ${chalk.cyan(`[${name}]`)}` : '';
|
|
3162
3422
|
const textBeanFullName = beanFullName ? ` ${chalk.gray(`[${beanFullName}]`)}` : '';
|
|
@@ -3165,9 +3425,10 @@ const formatLoggerConsole = clientInfo => {
|
|
|
3165
3425
|
const textDurationMs = durationMs !== undefined ? ` ${chalk.cyan(`+${durationMs}ms`)}` : '';
|
|
3166
3426
|
const textStack = stack ? `\n${stack}` : '';
|
|
3167
3427
|
return `${timestamp} ${level}${textClientName}${textName}${textBeanFullName}${textMeta}${textMessage}${textDurationMs}${textStack}`;
|
|
3168
|
-
});
|
|
3428
|
+
}));
|
|
3169
3429
|
};
|
|
3170
3430
|
function __formatLoggerFilterCheckInfo(info) {
|
|
3431
|
+
if (getRequestBoundary()) return false;
|
|
3171
3432
|
if (typeof info.message === 'function') {
|
|
3172
3433
|
if (info.message[SymbolLoggerMessage] === undefined) {
|
|
3173
3434
|
info.message[SymbolLoggerMessage] = info.message();
|
|
@@ -4237,6 +4498,156 @@ function assertTestClaimRedisClient(app, clientName, options) {
|
|
|
4237
4498
|
if (plan.kind === 'client' ? options.keyPrefix !== plan.prefix : options.keyPrefix !== undefined) throw new Error('Unregistered managed Redis key prefix');
|
|
4238
4499
|
}
|
|
4239
4500
|
|
|
4501
|
+
const freshApps = new WeakSet();
|
|
4502
|
+
function assertFreshTestResourcesReady(app) {
|
|
4503
|
+
if (!requiresFreshTestResources()) return;
|
|
4504
|
+
if (!freshApps.has(app)) throw new Error('Strict test resource freshness was not established');
|
|
4505
|
+
}
|
|
4506
|
+
async function assertEmptyDirectory(directory) {
|
|
4507
|
+
assertIsolatedTestPath(directory, directory);
|
|
4508
|
+
try {
|
|
4509
|
+
if (!(await lstat(directory)).isDirectory() || (await readdir(directory)).length !== 0) {
|
|
4510
|
+
throw new Error('Strict test local resource is not fresh');
|
|
4511
|
+
}
|
|
4512
|
+
} catch (error) {
|
|
4513
|
+
if (error.code !== 'ENOENT') throw error;
|
|
4514
|
+
}
|
|
4515
|
+
}
|
|
4516
|
+
|
|
4517
|
+
// Runs after configuration, before module loading (including worker-alive Redis writes).
|
|
4518
|
+
// This is a freshness snapshot, not a lease against independent external writers.
|
|
4519
|
+
async function assertFreshTestResources(app, dependencies) {
|
|
4520
|
+
if (!requiresFreshTestResources()) return;
|
|
4521
|
+
if (!app.meta.env.VONA_TEST_RUN_ID || app.meta.appReady || freshApps.has(app)) {
|
|
4522
|
+
throw new Error('Strict test freshness requires a new isolated startup');
|
|
4523
|
+
}
|
|
4524
|
+
assertIsolatedTestConfig(app);
|
|
4525
|
+
const config = app.config;
|
|
4526
|
+
const paths = resolveVonaLocalPaths(app.projectPath, 'test', app.meta.env.VONA_TEST_RUN_ID);
|
|
4527
|
+
for (const directory of [paths.publicRoot, paths.runtimeRoot, paths.sqliteRoot]) {
|
|
4528
|
+
await assertEmptyDirectory(directory);
|
|
4529
|
+
}
|
|
4530
|
+
const inventory = buildTestClaimResourceInventory(app);
|
|
4531
|
+
const readers = dependencies ?? createFreshnessDependencies(app.projectPath);
|
|
4532
|
+
try {
|
|
4533
|
+
for (const plan of inventory.databases) {
|
|
4534
|
+
if (plan.endpoint.dialect === 'better-sqlite3') continue;
|
|
4535
|
+
const options = mergeTestClaimDatabaseOptions(config.database.base, undefined, config.database.clients[plan.clientName]);
|
|
4536
|
+
if (await readers.databaseExists(plan, options)) {
|
|
4537
|
+
throw new Error('Preexisting strict test database namespace');
|
|
4538
|
+
}
|
|
4539
|
+
}
|
|
4540
|
+
for (const resource of inventory.redis) {
|
|
4541
|
+
const options = mergeTestClaimRedisOptions(config.redis.base, config.redis.clients[resource.clientName]);
|
|
4542
|
+
if (await readers.redisExists(resource, options)) {
|
|
4543
|
+
throw new Error('Preexisting strict test Redis namespace');
|
|
4544
|
+
}
|
|
4545
|
+
}
|
|
4546
|
+
} catch {
|
|
4547
|
+
// Driver messages may embed credentials, database names or Redis keys.
|
|
4548
|
+
throw new Error('Strict test resource freshness check failed');
|
|
4549
|
+
}
|
|
4550
|
+
freshApps.add(app);
|
|
4551
|
+
}
|
|
4552
|
+
async function hasFreshnessDatabase(connection, plan) {
|
|
4553
|
+
try {
|
|
4554
|
+
const pg = plan.endpoint.dialect === 'pg';
|
|
4555
|
+
if (!pg && !['mysql', 'mysql2'].includes(plan.endpoint.dialect)) {
|
|
4556
|
+
throw new Error('Unsupported freshness database dialect');
|
|
4557
|
+
}
|
|
4558
|
+
const result = await connection.raw(pg ? 'select datname from pg_database where left(datname, ?) = ? limit 1' : 'select schema_name from information_schema.schemata where left(schema_name, ?) = ? limit 1', [plan.prefix.length, plan.prefix]);
|
|
4559
|
+
const rows = pg ? result?.rows : result?.[0];
|
|
4560
|
+
if (!Array.isArray(rows)) throw new Error('Invalid freshness database response');
|
|
4561
|
+
return rows.length !== 0;
|
|
4562
|
+
} finally {
|
|
4563
|
+
await connection.destroy();
|
|
4564
|
+
}
|
|
4565
|
+
}
|
|
4566
|
+
async function hasFreshnessRedis(connection, prefix, maxScans = 10000) {
|
|
4567
|
+
if (!prefix || !Number.isSafeInteger(maxScans) || maxScans < 1) {
|
|
4568
|
+
throw new Error('Invalid Redis freshness bounds');
|
|
4569
|
+
}
|
|
4570
|
+
const pattern = `${prefix.replace(/[\\*?[\]]/g, '\\$&')}*`;
|
|
4571
|
+
let cursor = '0';
|
|
4572
|
+
for (let scans = 0; scans < maxScans; scans++) {
|
|
4573
|
+
const result = await connection.scan(cursor, 'MATCH', pattern, 'COUNT', 128);
|
|
4574
|
+
if (!Array.isArray(result) || result.length !== 2 || typeof result[0] !== 'string' || !/^\d+$/.test(result[0]) || !Array.isArray(result[1]) || result[1].some(key => typeof key !== 'string' || !key.startsWith(prefix))) {
|
|
4575
|
+
throw new Error('Invalid Redis freshness response');
|
|
4576
|
+
}
|
|
4577
|
+
if (result[1].length !== 0) return true;
|
|
4578
|
+
cursor = result[0];
|
|
4579
|
+
if (cursor === '0') return false;
|
|
4580
|
+
}
|
|
4581
|
+
throw new Error('Redis freshness scan limit exceeded');
|
|
4582
|
+
}
|
|
4583
|
+
function createFreshnessDependencies(projectPath, load = createRequire(path.join(projectPath, 'package.json'))) {
|
|
4584
|
+
return {
|
|
4585
|
+
async databaseExists(plan, options) {
|
|
4586
|
+
const endpoint = plan.endpoint;
|
|
4587
|
+
const source = options.connection;
|
|
4588
|
+
const target = {
|
|
4589
|
+
user: source.user,
|
|
4590
|
+
password: source.password,
|
|
4591
|
+
ssl: source.ssl,
|
|
4592
|
+
database: endpoint.adminDatabase,
|
|
4593
|
+
connectionTimeoutMillis: 10000,
|
|
4594
|
+
connectTimeout: 10000
|
|
4595
|
+
};
|
|
4596
|
+
if (endpoint.transport === 'tcp') {
|
|
4597
|
+
target.host = endpoint.host;
|
|
4598
|
+
target.port = endpoint.port;
|
|
4599
|
+
} else if (endpoint.dialect === 'pg') {
|
|
4600
|
+
target.host = endpoint.socketPath;
|
|
4601
|
+
target.port = endpoint.port;
|
|
4602
|
+
} else {
|
|
4603
|
+
target.socketPath = endpoint.socketPath;
|
|
4604
|
+
}
|
|
4605
|
+
const knex = load('knex');
|
|
4606
|
+
return await hasFreshnessDatabase(knex({
|
|
4607
|
+
client: endpoint.dialect,
|
|
4608
|
+
connection: target,
|
|
4609
|
+
pool: {
|
|
4610
|
+
min: 0,
|
|
4611
|
+
max: 1
|
|
4612
|
+
},
|
|
4613
|
+
acquireConnectionTimeout: 10000
|
|
4614
|
+
}), plan);
|
|
4615
|
+
},
|
|
4616
|
+
async redisExists(resource, options) {
|
|
4617
|
+
const endpoint = resource.endpoint;
|
|
4618
|
+
const target = endpoint.transport === 'socket' ? {
|
|
4619
|
+
path: endpoint.socketPath
|
|
4620
|
+
} : {
|
|
4621
|
+
host: endpoint.host,
|
|
4622
|
+
port: endpoint.port
|
|
4623
|
+
};
|
|
4624
|
+
const Redis = load('ioredis').Redis;
|
|
4625
|
+
const connection = new Redis({
|
|
4626
|
+
...target,
|
|
4627
|
+
username: options.username,
|
|
4628
|
+
password: options.password,
|
|
4629
|
+
tls: options.tls,
|
|
4630
|
+
db: endpoint.db,
|
|
4631
|
+
keyPrefix: undefined,
|
|
4632
|
+
lazyConnect: true,
|
|
4633
|
+
connectTimeout: 10000,
|
|
4634
|
+
commandTimeout: 10000,
|
|
4635
|
+
retryStrategy: () => null,
|
|
4636
|
+
maxRetriesPerRequest: 0,
|
|
4637
|
+
enableOfflineQueue: false
|
|
4638
|
+
});
|
|
4639
|
+
connection.on('error', () => {});
|
|
4640
|
+
try {
|
|
4641
|
+
await connection.connect();
|
|
4642
|
+
const prefix = resource.kind === 'queue' ? resource.prefix.slice(0, -'queue:'.length) : resource.prefix;
|
|
4643
|
+
return await hasFreshnessRedis(connection, prefix);
|
|
4644
|
+
} finally {
|
|
4645
|
+
connection.disconnect();
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4648
|
+
};
|
|
4649
|
+
}
|
|
4650
|
+
|
|
4240
4651
|
function jsxEnhance() {
|
|
4241
4652
|
const createElementOriginal = React.createElement;
|
|
4242
4653
|
React.createElement = function (...args) {
|
|
@@ -4456,6 +4867,7 @@ class Start {
|
|
|
4456
4867
|
const app = this.app;
|
|
4457
4868
|
try {
|
|
4458
4869
|
await this._start_appConfig();
|
|
4870
|
+
await assertFreshTestResources(app);
|
|
4459
4871
|
await this._start_appLoad();
|
|
4460
4872
|
await this._start_appStart();
|
|
4461
4873
|
await this._start_appReady();
|
|
@@ -4845,5 +5257,5 @@ async function retry(options, fn) {
|
|
|
4845
5257
|
|
|
4846
5258
|
zodExtendOpenApi();
|
|
4847
5259
|
|
|
4848
|
-
export { $Class, $localeScope, $makeLocaleMagic, $protocolKey, AppHmr, AppHmrDeps, AppLocale, AppLogger, AppMeta, AppMetadata, AppResource, AppUtil, BeanAopBase, BeanAopMethodBase, BeanBase, BeanBaseSimple, BeanContainer, BeanInfo, BeanScopeBase, BeanScopeContainer, BeanScopeError, BeanScopeErrorImpl, BeanScopeLocale, BeanScopeScene, BeanScopeUtil, BeanSimple, EnumAppEvent, ErrorClass, Global, LocaleModuleNameSeparator, PickClassInner, ProxyDisable, SymbolBeanContainerInstances, SymbolBeanFullName, SymbolBeanInstanceKey, SymbolBeanInstancePropsLazy, SymbolCacheAopChains, SymbolCacheAopChainsKey, SymbolDecoratorBeanFullName, SymbolDecoratorBeanInfo, SymbolDecoratorGlobal, SymbolDecoratorProxyDisable, SymbolDecoratorUse, SymbolDecoratorVirtual, SymbolHmrStateLoad, SymbolHmrStateSave, SymbolMappedClassMetadataKeys, SymbolModuleBelong, SymbolModuleName, TEST_CLAIM_DIRECTORY, TEST_CLAIM_TOKEN_ENV, TEST_CLAIM_URL_ENV, TEST_CLAIM_WINDOWS_GUARDIAN_ENV, TEST_RUN_ID_ENV, TEST_RUN_OWNER_FILE, Use, Virtual, VonaApplication, __prepareInjectSelectorInfo, appHmrDeps, appMetadata, appResource, assertDisposableDatabaseName, assertIsolatedTestConfig, assertIsolatedTestDatabaseClient, assertIsolatedTestPath, assertTestAppIdentity, assertTestClaimDatabaseClient, assertTestClaimRedisClient, assertTestClaimSafePath, assertTestClaimWindowsJob, assertTestRunOwner, beanFullNameFromOnionName, bootstrap, buildTestClaimResourceInventory, cast, closeApp, closeProcessWorker, combineConfigDefault, combineFilePathSafe, compose, copyAddon, copyMetadataOfClasses, copyProperties, copyPropertiesOfClasses, createApp, createAppMaster, createBeanDecorator, createGeneralApp, createHash, createTestClaim, deepExtend, disposeInstance, errorsInternal, filterHeaders, formatLoggerAxiosError, formatLoggerConsole, formatLoggerCtx, formatLoggerDummy, formatLoggerErrors, formatLoggerFilter, functionNoop, getDatabaseTestPrefix, getHomeVonaAppDir, getLoggerPathPhysicalRoot, getMappedClassMetadataKeys, getPlatformArch, getPublicPathPhysicalRoot, getRuntimePathPhysicalRoot, getSqlite3DatabaseNameDefault, getTestAppName, getTestClaimCreatedAt, getTestClaimPaths, getTestClaimRedisPrefix, getTestRunIdFromEnv, hasTestClaimTransport, instanceDesp, isLocaleMagic, loadJSONFile, loadTestClaim, mergeTestClaimDatabaseOptions, mergeTestClaimRedisOptions, normalizeTestClaimDatabaseEndpoint, normalizeTestClaimRedisEndpoint, onionNameFromBeanFullName, pathToHref, polyfillDispose, prepareEnv, registerMappedClassMetadataKey, registerTestClaimDatabaseCreation, registerTestClaimResourceInventory, registerTestClaimWindowsJobVerifier, requireAddon, requireDynamic, resolveAddon, resolveTestRunId, resolveVonaLocalPaths, resolverSharp, resolverSharpLibvips, resolverSqlite3, retry, saveJSONFile, sendTestClaimEvent, setMappedClassMetadataKeys, startTestClaimBroker, text$1 as text, useApp, usePrepareArg, usePrepareArgs, uuidv4, validateTestClaim, withIsolatedTestDatabaseDiscovery, writeTestClaim };
|
|
5260
|
+
export { $Class, $localeScope, $makeLocaleMagic, $protocolKey, AppHmr, AppHmrDeps, AppLocale, AppLogger, AppMeta, AppMetadata, AppResource, AppUtil, BeanAopBase, BeanAopMethodBase, BeanBase, BeanBaseSimple, BeanContainer, BeanInfo, BeanScopeBase, BeanScopeContainer, BeanScopeError, BeanScopeErrorImpl, BeanScopeLocale, BeanScopeScene, BeanScopeUtil, BeanSimple, EnumAppEvent, ErrorClass, Global, LocaleModuleNameSeparator, PickClassInner, ProxyDisable, SymbolBeanContainerInstances, SymbolBeanFullName, SymbolBeanInstanceKey, SymbolBeanInstancePropsLazy, SymbolCacheAopChains, SymbolCacheAopChainsKey, SymbolDecoratorBeanFullName, SymbolDecoratorBeanInfo, SymbolDecoratorGlobal, SymbolDecoratorProxyDisable, SymbolDecoratorUse, SymbolDecoratorVirtual, SymbolHmrStateLoad, SymbolHmrStateSave, SymbolMappedClassMetadataKeys, SymbolModuleBelong, SymbolModuleName, TEST_CLAIM_DIRECTORY, TEST_CLAIM_TOKEN_ENV, TEST_CLAIM_URL_ENV, TEST_CLAIM_WINDOWS_GUARDIAN_ENV, TEST_REQUIRE_FRESH_RESOURCES_ENV, TEST_RUN_ID_ENV, TEST_RUN_OWNER_FILE, Use, Virtual, VonaApplication, __prepareInjectSelectorInfo, appHmrDeps, appMetadata, appResource, assertDisposableDatabaseName, assertFreshTestResources, assertFreshTestResourcesReady, assertIsolatedTestConfig, assertIsolatedTestDatabaseClient, assertIsolatedTestPath, assertTestAppIdentity, assertTestClaimDatabaseClient, assertTestClaimRedisClient, assertTestClaimSafePath, assertTestClaimWindowsJob, assertTestRunOwner, beanFullNameFromOnionName, bootstrap, buildTestClaimResourceInventory, captureRequestBoundary, cast, closeApp, closeProcessWorker, combineConfigDefault, combineFilePathSafe, compose, copyAddon, copyMetadataOfClasses, copyProperties, copyPropertiesOfClasses, createApp, createAppMaster, createBeanDecorator, createDiagnosticLogger, createFreshnessDependencies, createGeneralApp, createHash, createRequestBoundary, createTestClaim, deepExtend, disposeInstance, errorsInternal, filterHeaders, formatLoggerAxiosError, formatLoggerBoundary, formatLoggerConsole, formatLoggerCtx, formatLoggerDummy, formatLoggerErrors, formatLoggerFilter, functionNoop, getDatabaseTestPrefix, getHomeVonaAppDir, getLoggerPathPhysicalRoot, getMappedClassMetadataKeys, getPlatformArch, getPublicPathPhysicalRoot, getRequestBoundary, getRuntimePathPhysicalRoot, getSqlite3DatabaseNameDefault, getSuppressedDiagnosticLogger, getTestAppName, getTestClaimCreatedAt, getTestClaimPaths, getTestClaimRedisPrefix, getTestRunIdFromEnv, hasFreshnessDatabase, hasFreshnessRedis, hasTestClaimTransport, instanceDesp, isLocaleMagic, loadJSONFile, loadTestClaim, mergeTestClaimDatabaseOptions, mergeTestClaimRedisOptions, normalizeTestClaimDatabaseEndpoint, normalizeTestClaimRedisEndpoint, onionNameFromBeanFullName, pathToHref, polyfillDispose, prepareEnv, registerMappedClassMetadataKey, registerTestClaimDatabaseCreation, registerTestClaimResourceInventory, registerTestClaimWindowsJobVerifier, requireAddon, requireDynamic, requiresFreshTestResources, resolveAddon, resolveTestRunId, resolveVonaLocalPaths, resolverSharp, resolverSharpLibvips, resolverSqlite3, retry, runWithRequestBoundary, saveJSONFile, sendTestClaimEvent, setMappedClassMetadataKeys, startTestClaimBroker, text$1 as text, useApp, usePrepareArg, usePrepareArgs, uuidv4, validateTestClaim, withIsolatedTestDatabaseDiscovery, writeTestClaim };
|
|
4849
5261
|
//# sourceMappingURL=index.js.map
|