vona-core 5.1.47 → 5.1.48
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 +431 -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 +21 -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/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 +11 -3
- package/src/lib/core/logger/diagnosticLogger.test.ts +146 -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 +238 -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
|
|
|
@@ -2647,25 +2873,31 @@ class AppHmr extends BeanSimple {
|
|
|
2647
2873
|
}
|
|
2648
2874
|
|
|
2649
2875
|
const SymbolLoggerInstances = Symbol('SymbolLoggerInstances');
|
|
2876
|
+
const SymbolRawLoggerInstances = Symbol('SymbolRawLoggerInstances');
|
|
2650
2877
|
class AppLogger extends BeanSimple {
|
|
2651
2878
|
constructor(...args) {
|
|
2652
2879
|
super(...args);
|
|
2653
2880
|
this[SymbolLoggerInstances] = {};
|
|
2881
|
+
this[SymbolRawLoggerInstances] = {};
|
|
2654
2882
|
}
|
|
2655
2883
|
async dispose() {
|
|
2656
|
-
for (const key in this[
|
|
2657
|
-
const logger = this[
|
|
2884
|
+
for (const key in this[SymbolRawLoggerInstances]) {
|
|
2885
|
+
const logger = this[SymbolRawLoggerInstances][key];
|
|
2658
2886
|
await _closeLogger(logger);
|
|
2659
2887
|
}
|
|
2660
2888
|
}
|
|
2661
2889
|
get(clientName) {
|
|
2890
|
+
if (getRequestBoundary()) return getSuppressedDiagnosticLogger();
|
|
2662
2891
|
clientName = clientName || 'default';
|
|
2663
2892
|
if (!this[SymbolLoggerInstances][clientName]) {
|
|
2664
|
-
|
|
2893
|
+
const raw = this._createClient(clientName);
|
|
2894
|
+
this[SymbolRawLoggerInstances][clientName] = raw;
|
|
2895
|
+
this[SymbolLoggerInstances][clientName] = createDiagnosticLogger(raw);
|
|
2665
2896
|
}
|
|
2666
2897
|
return this[SymbolLoggerInstances][clientName];
|
|
2667
2898
|
}
|
|
2668
2899
|
child(childName, clientName) {
|
|
2900
|
+
if (getRequestBoundary()) return getSuppressedDiagnosticLogger();
|
|
2669
2901
|
const logger = this.get(clientName);
|
|
2670
2902
|
if (!childName) return logger;
|
|
2671
2903
|
return logger.child({
|
|
@@ -3081,10 +3313,16 @@ var localesDefault = {
|
|
|
3081
3313
|
};
|
|
3082
3314
|
|
|
3083
3315
|
const SymbolLoggerMessage = Symbol('SymbolLoggerMessage');
|
|
3084
|
-
const
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
})
|
|
3316
|
+
const formatLoggerBoundary = Winston.format(info => {
|
|
3317
|
+
if (getRequestBoundary()) return false;
|
|
3318
|
+
return info;
|
|
3319
|
+
});
|
|
3320
|
+
const formatLoggerAxiosError = Winston.format((einfo, opts) => {
|
|
3321
|
+
if (getRequestBoundary()) return false;
|
|
3322
|
+
const {
|
|
3323
|
+
stack,
|
|
3324
|
+
cause
|
|
3325
|
+
} = opts;
|
|
3088
3326
|
if (einfo instanceof Error && einfo.constructor.name.includes('AxiosError') || einfo.name === 'AxiosError' || einfo.isAxiosError) {
|
|
3089
3327
|
const info = Object.assign({}, einfo, {
|
|
3090
3328
|
level: einfo.level,
|
|
@@ -3104,9 +3342,11 @@ const formatLoggerAxiosError = Winston.format((einfo, {
|
|
|
3104
3342
|
return einfo;
|
|
3105
3343
|
});
|
|
3106
3344
|
const formatLoggerErrors = opts => {
|
|
3107
|
-
|
|
3345
|
+
if (getRequestBoundary()) return formatLoggerBoundary();
|
|
3346
|
+
return Winston.format.combine(formatLoggerBoundary(), formatLoggerAxiosError(opts), Winston.format.errors(opts));
|
|
3108
3347
|
};
|
|
3109
3348
|
const formatLoggerCtx = Winston.format((info, _opts) => {
|
|
3349
|
+
if (getRequestBoundary()) return false;
|
|
3110
3350
|
const app = useApp();
|
|
3111
3351
|
if (!app.ctx) return info;
|
|
3112
3352
|
if (app.ctx.method) info.method = app.ctx.method;
|
|
@@ -3123,11 +3363,13 @@ const formatLoggerCtx = Winston.format((info, _opts) => {
|
|
|
3123
3363
|
return info;
|
|
3124
3364
|
});
|
|
3125
3365
|
const formatLoggerDummy = Winston.format(info => {
|
|
3366
|
+
if (getRequestBoundary()) return false;
|
|
3126
3367
|
const app = useApp();
|
|
3127
3368
|
if (app.meta.env.LOGGER_DUMMY === 'true') return false;
|
|
3128
3369
|
return info;
|
|
3129
3370
|
});
|
|
3130
3371
|
const formatLoggerFilter = Winston.format((info, opts) => {
|
|
3372
|
+
if (getRequestBoundary()) return false;
|
|
3131
3373
|
// check client
|
|
3132
3374
|
const child = typeof opts.child === 'function' ? opts.child() : opts.child;
|
|
3133
3375
|
if (child && !child.includes(info.name)) {
|
|
@@ -3147,16 +3389,18 @@ const formatLoggerFilter = Winston.format((info, opts) => {
|
|
|
3147
3389
|
return false;
|
|
3148
3390
|
});
|
|
3149
3391
|
const formatLoggerConsole = clientInfo => {
|
|
3150
|
-
return Winston.format.printf(
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3392
|
+
return Winston.format.combine(formatLoggerBoundary(), Winston.format.printf(info => {
|
|
3393
|
+
if (getRequestBoundary()) return '';
|
|
3394
|
+
const {
|
|
3395
|
+
timestamp,
|
|
3396
|
+
level,
|
|
3397
|
+
stack,
|
|
3398
|
+
message,
|
|
3399
|
+
name,
|
|
3400
|
+
beanFullName,
|
|
3401
|
+
durationMs,
|
|
3402
|
+
...meta
|
|
3403
|
+
} = info;
|
|
3160
3404
|
const textClientName = clientInfo.clientName === 'default' ? '' : ` ${chalk.cyan(`[${clientInfo.clientName}]`)}`;
|
|
3161
3405
|
const textName = name ? ` ${chalk.cyan(`[${name}]`)}` : '';
|
|
3162
3406
|
const textBeanFullName = beanFullName ? ` ${chalk.gray(`[${beanFullName}]`)}` : '';
|
|
@@ -3165,9 +3409,10 @@ const formatLoggerConsole = clientInfo => {
|
|
|
3165
3409
|
const textDurationMs = durationMs !== undefined ? ` ${chalk.cyan(`+${durationMs}ms`)}` : '';
|
|
3166
3410
|
const textStack = stack ? `\n${stack}` : '';
|
|
3167
3411
|
return `${timestamp} ${level}${textClientName}${textName}${textBeanFullName}${textMeta}${textMessage}${textDurationMs}${textStack}`;
|
|
3168
|
-
});
|
|
3412
|
+
}));
|
|
3169
3413
|
};
|
|
3170
3414
|
function __formatLoggerFilterCheckInfo(info) {
|
|
3415
|
+
if (getRequestBoundary()) return false;
|
|
3171
3416
|
if (typeof info.message === 'function') {
|
|
3172
3417
|
if (info.message[SymbolLoggerMessage] === undefined) {
|
|
3173
3418
|
info.message[SymbolLoggerMessage] = info.message();
|
|
@@ -4237,6 +4482,164 @@ function assertTestClaimRedisClient(app, clientName, options) {
|
|
|
4237
4482
|
if (plan.kind === 'client' ? options.keyPrefix !== plan.prefix : options.keyPrefix !== undefined) throw new Error('Unregistered managed Redis key prefix');
|
|
4238
4483
|
}
|
|
4239
4484
|
|
|
4485
|
+
const TEST_REQUIRE_FRESH_RESOURCES_ENV = 'VONA_TEST_REQUIRE_FRESH_RESOURCES';
|
|
4486
|
+
const freshApps = new WeakSet();
|
|
4487
|
+
function requiresFreshTestResources() {
|
|
4488
|
+
const value = process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
|
|
4489
|
+
if (value !== undefined && value !== 'true') {
|
|
4490
|
+
throw new Error('Invalid strict test resource freshness flag');
|
|
4491
|
+
}
|
|
4492
|
+
return value === 'true';
|
|
4493
|
+
}
|
|
4494
|
+
function assertFreshTestResourcesReady(app) {
|
|
4495
|
+
if (!requiresFreshTestResources()) return;
|
|
4496
|
+
if (!freshApps.has(app)) throw new Error('Strict test resource freshness was not established');
|
|
4497
|
+
}
|
|
4498
|
+
async function assertEmptyDirectory(directory) {
|
|
4499
|
+
assertIsolatedTestPath(directory, directory);
|
|
4500
|
+
try {
|
|
4501
|
+
if (!(await lstat(directory)).isDirectory() || (await readdir(directory)).length !== 0) {
|
|
4502
|
+
throw new Error('Strict test local resource is not fresh');
|
|
4503
|
+
}
|
|
4504
|
+
} catch (error) {
|
|
4505
|
+
if (error.code !== 'ENOENT') throw error;
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4508
|
+
|
|
4509
|
+
// Runs after configuration, before module loading (including worker-alive Redis writes).
|
|
4510
|
+
// This is a freshness snapshot, not a lease against independent external writers.
|
|
4511
|
+
async function assertFreshTestResources(app, dependencies) {
|
|
4512
|
+
if (!requiresFreshTestResources()) return;
|
|
4513
|
+
if (!app.meta.env.VONA_TEST_RUN_ID || app.meta.appReady || freshApps.has(app)) {
|
|
4514
|
+
throw new Error('Strict test freshness requires a new isolated startup');
|
|
4515
|
+
}
|
|
4516
|
+
assertIsolatedTestConfig(app);
|
|
4517
|
+
const config = app.config;
|
|
4518
|
+
const paths = resolveVonaLocalPaths(app.projectPath, 'test', app.meta.env.VONA_TEST_RUN_ID);
|
|
4519
|
+
for (const directory of [paths.publicRoot, paths.runtimeRoot, paths.sqliteRoot]) {
|
|
4520
|
+
await assertEmptyDirectory(directory);
|
|
4521
|
+
}
|
|
4522
|
+
const inventory = buildTestClaimResourceInventory(app);
|
|
4523
|
+
const readers = dependencies ?? createFreshnessDependencies(app.projectPath);
|
|
4524
|
+
try {
|
|
4525
|
+
for (const plan of inventory.databases) {
|
|
4526
|
+
if (plan.endpoint.dialect === 'better-sqlite3') continue;
|
|
4527
|
+
const options = mergeTestClaimDatabaseOptions(config.database.base, undefined, config.database.clients[plan.clientName]);
|
|
4528
|
+
if (await readers.databaseExists(plan, options)) {
|
|
4529
|
+
throw new Error('Preexisting strict test database namespace');
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
for (const resource of inventory.redis) {
|
|
4533
|
+
const options = mergeTestClaimRedisOptions(config.redis.base, config.redis.clients[resource.clientName]);
|
|
4534
|
+
if (await readers.redisExists(resource, options)) {
|
|
4535
|
+
throw new Error('Preexisting strict test Redis namespace');
|
|
4536
|
+
}
|
|
4537
|
+
}
|
|
4538
|
+
} catch {
|
|
4539
|
+
// Driver messages may embed credentials, database names or Redis keys.
|
|
4540
|
+
throw new Error('Strict test resource freshness check failed');
|
|
4541
|
+
}
|
|
4542
|
+
freshApps.add(app);
|
|
4543
|
+
}
|
|
4544
|
+
async function hasFreshnessDatabase(connection, plan) {
|
|
4545
|
+
try {
|
|
4546
|
+
const pg = plan.endpoint.dialect === 'pg';
|
|
4547
|
+
if (!pg && !['mysql', 'mysql2'].includes(plan.endpoint.dialect)) {
|
|
4548
|
+
throw new Error('Unsupported freshness database dialect');
|
|
4549
|
+
}
|
|
4550
|
+
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]);
|
|
4551
|
+
const rows = pg ? result?.rows : result?.[0];
|
|
4552
|
+
if (!Array.isArray(rows)) throw new Error('Invalid freshness database response');
|
|
4553
|
+
return rows.length !== 0;
|
|
4554
|
+
} finally {
|
|
4555
|
+
await connection.destroy();
|
|
4556
|
+
}
|
|
4557
|
+
}
|
|
4558
|
+
async function hasFreshnessRedis(connection, prefix, maxScans = 10000) {
|
|
4559
|
+
if (!prefix || !Number.isSafeInteger(maxScans) || maxScans < 1) {
|
|
4560
|
+
throw new Error('Invalid Redis freshness bounds');
|
|
4561
|
+
}
|
|
4562
|
+
const pattern = `${prefix.replace(/[\\*?[\]]/g, '\\$&')}*`;
|
|
4563
|
+
let cursor = '0';
|
|
4564
|
+
for (let scans = 0; scans < maxScans; scans++) {
|
|
4565
|
+
const result = await connection.scan(cursor, 'MATCH', pattern, 'COUNT', 128);
|
|
4566
|
+
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))) {
|
|
4567
|
+
throw new Error('Invalid Redis freshness response');
|
|
4568
|
+
}
|
|
4569
|
+
if (result[1].length !== 0) return true;
|
|
4570
|
+
cursor = result[0];
|
|
4571
|
+
if (cursor === '0') return false;
|
|
4572
|
+
}
|
|
4573
|
+
throw new Error('Redis freshness scan limit exceeded');
|
|
4574
|
+
}
|
|
4575
|
+
function createFreshnessDependencies(projectPath, load = createRequire(path.join(projectPath, 'package.json'))) {
|
|
4576
|
+
return {
|
|
4577
|
+
async databaseExists(plan, options) {
|
|
4578
|
+
const endpoint = plan.endpoint;
|
|
4579
|
+
const source = options.connection;
|
|
4580
|
+
const target = {
|
|
4581
|
+
user: source.user,
|
|
4582
|
+
password: source.password,
|
|
4583
|
+
ssl: source.ssl,
|
|
4584
|
+
database: endpoint.adminDatabase,
|
|
4585
|
+
connectionTimeoutMillis: 10000,
|
|
4586
|
+
connectTimeout: 10000
|
|
4587
|
+
};
|
|
4588
|
+
if (endpoint.transport === 'tcp') {
|
|
4589
|
+
target.host = endpoint.host;
|
|
4590
|
+
target.port = endpoint.port;
|
|
4591
|
+
} else if (endpoint.dialect === 'pg') {
|
|
4592
|
+
target.host = endpoint.socketPath;
|
|
4593
|
+
target.port = endpoint.port;
|
|
4594
|
+
} else {
|
|
4595
|
+
target.socketPath = endpoint.socketPath;
|
|
4596
|
+
}
|
|
4597
|
+
const knex = load('knex');
|
|
4598
|
+
return await hasFreshnessDatabase(knex({
|
|
4599
|
+
client: endpoint.dialect,
|
|
4600
|
+
connection: target,
|
|
4601
|
+
pool: {
|
|
4602
|
+
min: 0,
|
|
4603
|
+
max: 1
|
|
4604
|
+
},
|
|
4605
|
+
acquireConnectionTimeout: 10000
|
|
4606
|
+
}), plan);
|
|
4607
|
+
},
|
|
4608
|
+
async redisExists(resource, options) {
|
|
4609
|
+
const endpoint = resource.endpoint;
|
|
4610
|
+
const target = endpoint.transport === 'socket' ? {
|
|
4611
|
+
path: endpoint.socketPath
|
|
4612
|
+
} : {
|
|
4613
|
+
host: endpoint.host,
|
|
4614
|
+
port: endpoint.port
|
|
4615
|
+
};
|
|
4616
|
+
const Redis = load('ioredis').Redis;
|
|
4617
|
+
const connection = new Redis({
|
|
4618
|
+
...target,
|
|
4619
|
+
username: options.username,
|
|
4620
|
+
password: options.password,
|
|
4621
|
+
tls: options.tls,
|
|
4622
|
+
db: endpoint.db,
|
|
4623
|
+
keyPrefix: undefined,
|
|
4624
|
+
lazyConnect: true,
|
|
4625
|
+
connectTimeout: 10000,
|
|
4626
|
+
commandTimeout: 10000,
|
|
4627
|
+
retryStrategy: () => null,
|
|
4628
|
+
maxRetriesPerRequest: 0,
|
|
4629
|
+
enableOfflineQueue: false
|
|
4630
|
+
});
|
|
4631
|
+
connection.on('error', () => {});
|
|
4632
|
+
try {
|
|
4633
|
+
await connection.connect();
|
|
4634
|
+
const prefix = resource.kind === 'queue' ? resource.prefix.slice(0, -'queue:'.length) : resource.prefix;
|
|
4635
|
+
return await hasFreshnessRedis(connection, prefix);
|
|
4636
|
+
} finally {
|
|
4637
|
+
connection.disconnect();
|
|
4638
|
+
}
|
|
4639
|
+
}
|
|
4640
|
+
};
|
|
4641
|
+
}
|
|
4642
|
+
|
|
4240
4643
|
function jsxEnhance() {
|
|
4241
4644
|
const createElementOriginal = React.createElement;
|
|
4242
4645
|
React.createElement = function (...args) {
|
|
@@ -4456,6 +4859,7 @@ class Start {
|
|
|
4456
4859
|
const app = this.app;
|
|
4457
4860
|
try {
|
|
4458
4861
|
await this._start_appConfig();
|
|
4862
|
+
await assertFreshTestResources(app);
|
|
4459
4863
|
await this._start_appLoad();
|
|
4460
4864
|
await this._start_appStart();
|
|
4461
4865
|
await this._start_appReady();
|
|
@@ -4845,5 +5249,5 @@ async function retry(options, fn) {
|
|
|
4845
5249
|
|
|
4846
5250
|
zodExtendOpenApi();
|
|
4847
5251
|
|
|
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 };
|
|
5252
|
+
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
5253
|
//# sourceMappingURL=index.js.map
|