cabloy 5.1.218 → 5.1.220

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/.cabloy-version +1 -1
  2. package/.github/workflows/create-cabloy.yml +1 -1
  3. package/CHANGELOG.md +19 -0
  4. package/README.md +1 -1
  5. package/package.json +1 -1
  6. package/repo-docs/fullstack/quickstart.md +3 -3
  7. package/repo-docs/reference/repo-scripts.md +12 -0
  8. package/scripts/generateOpenapiIsolated.test.ts +25 -0
  9. package/scripts/generateOpenapiIsolated.ts +28 -11
  10. package/scripts/runIsolatedTest.test.ts +157 -45
  11. package/scripts/runIsolatedTest.ts +30 -6
  12. package/scripts/strictGenerationStartup.test.ts +212 -0
  13. package/vona/packages-cli/cli/package.json +1 -1
  14. package/vona/packages-cli/cli-set-api/package.json +1 -1
  15. package/vona/packages-vona/vona/package.json +1 -1
  16. package/vona/packages-vona/vona-core/package.json +1 -1
  17. package/vona/packages-vona/vona-core/src/lib/bean/beanBase.ts +4 -0
  18. package/vona/packages-vona/vona-core/src/lib/core/asyncLocalStorage.ts +84 -12
  19. package/vona/packages-vona/vona-core/src/lib/core/index.ts +14 -0
  20. package/vona/packages-vona/vona-core/src/lib/core/logger/appLogger.ts +11 -3
  21. package/vona/packages-vona/vona-core/src/lib/core/logger/diagnosticLogger.test.ts +146 -0
  22. package/vona/packages-vona/vona-core/src/lib/core/logger/diagnosticLogger.ts +138 -0
  23. package/vona/packages-vona/vona-core/src/lib/core/logger/index.ts +1 -0
  24. package/vona/packages-vona/vona-core/src/lib/core/logger/utils.ts +25 -5
  25. package/vona/packages-vona/vona-core/src/lib/core/requestBoundary.ts +89 -0
  26. package/vona/packages-vona/vona-core/src/lib/core/testFreshness.test.ts +379 -0
  27. package/vona/packages-vona/vona-core/src/lib/core/testFreshness.ts +238 -0
  28. package/vona/packages-vona/vona-core/src/lib/framework/start.test.ts +4 -3
  29. package/vona/packages-vona/vona-core/src/lib/framework/start.ts +2 -0
  30. package/vona/packages-vona/vona-mock/package.json +1 -1
  31. package/vona/pnpm-lock.yaml +218 -43
  32. package/vona/pnpm-workspace.yaml +1 -1
  33. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  34. package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +1 -1
  35. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/bean/bean.executor.ts +111 -60
  36. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/service/executor.ts +6 -6
  37. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/types/executor.ts +13 -1
  38. package/vona/src/suite-vendor/a-vona/modules/a-jwt/package.json +1 -1
  39. package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/lib/jwtClientLogging.test.ts +182 -0
  40. package/vona/src/suite-vendor/a-vona/modules/a-jwt/src/service/jwtClient.ts +2 -8
  41. package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
  42. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/aopMethod.log.ts +4 -1
  43. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/bean.logger.ts +9 -0
  44. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/middlewareSystem.httpLog.ts +27 -14
  45. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactHttpLog.test.ts +207 -0
  46. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactHttpLog.ts +30 -0
  47. package/vona/src/suite-vendor/a-vona/modules/a-logger/test/middlewareSystem.httpLog.test.ts +33 -1
  48. package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
  49. package/vona/src/suite-vendor/a-vona/modules/a-startup/src/service/startup.ts +6 -0
  50. package/vona/src/suite-vendor/a-vona/modules/a-startup/test/devSeed.test.ts +2 -2
  51. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/package.json +1 -1
  52. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/bean.telemetry.ts +8 -1
  53. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/middlewareSystem.trace.ts +9 -14
  54. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/lib/spanDiagnostics.ts +70 -0
  55. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/service/telemetry.ts +37 -23
  56. package/vona/src/suite-vendor/a-vona/modules/a-version/package.json +1 -1
  57. package/vona/src/suite-vendor/a-vona/modules/a-version/src/service/database.ts +31 -4
  58. package/vona/src/suite-vendor/a-vona/package.json +2 -2
  59. package/zova/packages-cli/cli/package.json +2 -2
  60. package/zova/packages-cli/cli-set-front/package.json +1 -1
  61. package/zova/packages-cli/cli-set-front/src/lib/bean/cli.bin.buildRest.ts +11 -101
  62. package/zova/packages-cli/cli-set-front/src/lib/common/restDependencies.ts +78 -0
  63. package/zova/packages-cli/cli-set-front/src/lib/common/restDependencyVersions.ts +231 -0
  64. package/zova/packages-cli/cli-set-front/test/restDependencies.test.ts +431 -0
  65. package/zova/pnpm-lock.yaml +8 -8
  66. /package/vona/patches/{zova-core@5.1.92.patch → zova-core@5.1.93.patch} +0 -0
@@ -0,0 +1,238 @@
1
+ import { lstat, readdir } from 'node:fs/promises';
2
+ import { createRequire } from 'node:module';
3
+ import path from 'node:path';
4
+
5
+ import type { VonaApplication } from './application.ts';
6
+ import type { TestClaimDatabasePlan, TestClaimRedis } from './testClaim.ts';
7
+
8
+ import { assertIsolatedTestConfig, assertIsolatedTestPath } from './isolationGuard.ts';
9
+ import {
10
+ buildTestClaimResourceInventory,
11
+ mergeTestClaimDatabaseOptions,
12
+ mergeTestClaimRedisOptions,
13
+ } from './testClaim.ts';
14
+ import { resolveVonaLocalPaths } from './testIsolation.ts';
15
+
16
+ export const TEST_REQUIRE_FRESH_RESOURCES_ENV = 'VONA_TEST_REQUIRE_FRESH_RESOURCES';
17
+ const freshApps = new WeakSet<VonaApplication>();
18
+
19
+ export function requiresFreshTestResources() {
20
+ const value = process.env[TEST_REQUIRE_FRESH_RESOURCES_ENV];
21
+ if (value !== undefined && value !== 'true') {
22
+ throw new Error('Invalid strict test resource freshness flag');
23
+ }
24
+ return value === 'true';
25
+ }
26
+
27
+ export function assertFreshTestResourcesReady(app: VonaApplication) {
28
+ if (!requiresFreshTestResources()) return;
29
+ if (!freshApps.has(app)) throw new Error('Strict test resource freshness was not established');
30
+ }
31
+
32
+ export interface TestFreshnessDependencies {
33
+ databaseExists(plan: TestClaimDatabasePlan, options: Record<string, any>): Promise<boolean>;
34
+ redisExists(resource: TestClaimRedis, options: Record<string, any>): Promise<boolean>;
35
+ }
36
+
37
+ async function assertEmptyDirectory(directory: string) {
38
+ assertIsolatedTestPath(directory, directory);
39
+ try {
40
+ if (!(await lstat(directory)).isDirectory() || (await readdir(directory)).length !== 0) {
41
+ throw new Error('Strict test local resource is not fresh');
42
+ }
43
+ } catch (error) {
44
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
45
+ }
46
+ }
47
+
48
+ // Runs after configuration, before module loading (including worker-alive Redis writes).
49
+ // This is a freshness snapshot, not a lease against independent external writers.
50
+ export async function assertFreshTestResources(
51
+ app: VonaApplication,
52
+ dependencies?: TestFreshnessDependencies,
53
+ ) {
54
+ if (!requiresFreshTestResources()) return;
55
+ if (!app.meta.env.VONA_TEST_RUN_ID || app.meta.appReady || freshApps.has(app)) {
56
+ throw new Error('Strict test freshness requires a new isolated startup');
57
+ }
58
+ assertIsolatedTestConfig(app);
59
+ const config = app.config as typeof app.config & {
60
+ database: {
61
+ base: Record<string, unknown>;
62
+ clients: Record<string, Record<string, unknown>>;
63
+ };
64
+ redis: {
65
+ base: Record<string, unknown>;
66
+ clients: Record<string, Record<string, unknown>>;
67
+ };
68
+ };
69
+ const paths = resolveVonaLocalPaths(app.projectPath, 'test', app.meta.env.VONA_TEST_RUN_ID);
70
+ for (const directory of [paths.publicRoot, paths.runtimeRoot, paths.sqliteRoot]) {
71
+ await assertEmptyDirectory(directory);
72
+ }
73
+ const inventory = buildTestClaimResourceInventory(app);
74
+ const readers = dependencies ?? createFreshnessDependencies(app.projectPath);
75
+ try {
76
+ for (const plan of inventory.databases) {
77
+ if (plan.endpoint.dialect === 'better-sqlite3') continue;
78
+ const options = mergeTestClaimDatabaseOptions(
79
+ config.database.base,
80
+ undefined,
81
+ config.database.clients[plan.clientName],
82
+ );
83
+ if (await readers.databaseExists(plan, options)) {
84
+ throw new Error('Preexisting strict test database namespace');
85
+ }
86
+ }
87
+ for (const resource of inventory.redis) {
88
+ const options = mergeTestClaimRedisOptions(
89
+ config.redis.base,
90
+ config.redis.clients[resource.clientName],
91
+ );
92
+ if (await readers.redisExists(resource, options)) {
93
+ throw new Error('Preexisting strict test Redis namespace');
94
+ }
95
+ }
96
+ } catch {
97
+ // Driver messages may embed credentials, database names or Redis keys.
98
+ throw new Error('Strict test resource freshness check failed');
99
+ }
100
+ freshApps.add(app);
101
+ }
102
+
103
+ export interface FreshnessSqlConnection {
104
+ raw(sql: string, bindings: unknown[]): Promise<any>;
105
+ destroy(): Promise<unknown>;
106
+ }
107
+
108
+ export async function hasFreshnessDatabase(
109
+ connection: FreshnessSqlConnection,
110
+ plan: TestClaimDatabasePlan,
111
+ ) {
112
+ try {
113
+ const pg = plan.endpoint.dialect === 'pg';
114
+ if (!pg && !['mysql', 'mysql2'].includes(plan.endpoint.dialect)) {
115
+ throw new Error('Unsupported freshness database dialect');
116
+ }
117
+ const result = await connection.raw(
118
+ pg
119
+ ? 'select datname from pg_database where left(datname, ?) = ? limit 1'
120
+ : 'select schema_name from information_schema.schemata where left(schema_name, ?) = ? limit 1',
121
+ [plan.prefix.length, plan.prefix],
122
+ );
123
+ const rows = pg ? result?.rows : result?.[0];
124
+ if (!Array.isArray(rows)) throw new Error('Invalid freshness database response');
125
+ return rows.length !== 0;
126
+ } finally {
127
+ await connection.destroy();
128
+ }
129
+ }
130
+
131
+ export interface FreshnessRedisConnection {
132
+ scan(
133
+ cursor: string,
134
+ match: 'MATCH',
135
+ pattern: string,
136
+ count: 'COUNT',
137
+ size: number,
138
+ ): Promise<unknown>;
139
+ }
140
+
141
+ export async function hasFreshnessRedis(
142
+ connection: FreshnessRedisConnection,
143
+ prefix: string,
144
+ maxScans = 10000,
145
+ ) {
146
+ if (!prefix || !Number.isSafeInteger(maxScans) || maxScans < 1) {
147
+ throw new Error('Invalid Redis freshness bounds');
148
+ }
149
+ const pattern = `${prefix.replace(/[\\*?[\]]/g, '\\$&')}*`;
150
+ let cursor = '0';
151
+ for (let scans = 0; scans < maxScans; scans++) {
152
+ const result = await connection.scan(cursor, 'MATCH', pattern, 'COUNT', 128);
153
+ if (
154
+ !Array.isArray(result) ||
155
+ result.length !== 2 ||
156
+ typeof result[0] !== 'string' ||
157
+ !/^\d+$/.test(result[0]) ||
158
+ !Array.isArray(result[1]) ||
159
+ result[1].some(key => typeof key !== 'string' || !key.startsWith(prefix))
160
+ ) {
161
+ throw new Error('Invalid Redis freshness response');
162
+ }
163
+ if (result[1].length !== 0) return true;
164
+ cursor = result[0];
165
+ if (cursor === '0') return false;
166
+ }
167
+ throw new Error('Redis freshness scan limit exceeded');
168
+ }
169
+
170
+ export function createFreshnessDependencies(
171
+ projectPath: string,
172
+ load: (name: string) => any = createRequire(path.join(projectPath, 'package.json')),
173
+ ): TestFreshnessDependencies {
174
+ return {
175
+ async databaseExists(plan, options) {
176
+ const endpoint = plan.endpoint;
177
+ const source = options.connection;
178
+ const target: Record<string, any> = {
179
+ user: source.user,
180
+ password: source.password,
181
+ ssl: source.ssl,
182
+ database: endpoint.adminDatabase,
183
+ connectionTimeoutMillis: 10000,
184
+ connectTimeout: 10000,
185
+ };
186
+ if (endpoint.transport === 'tcp') {
187
+ target.host = endpoint.host;
188
+ target.port = endpoint.port;
189
+ } else if (endpoint.dialect === 'pg') {
190
+ target.host = endpoint.socketPath;
191
+ target.port = endpoint.port;
192
+ } else {
193
+ target.socketPath = endpoint.socketPath;
194
+ }
195
+ const knex = load('knex');
196
+ return await hasFreshnessDatabase(
197
+ knex({
198
+ client: endpoint.dialect,
199
+ connection: target,
200
+ pool: { min: 0, max: 1 },
201
+ acquireConnectionTimeout: 10000,
202
+ }),
203
+ plan,
204
+ );
205
+ },
206
+ async redisExists(resource, options) {
207
+ const endpoint = resource.endpoint;
208
+ const target =
209
+ endpoint.transport === 'socket'
210
+ ? { path: endpoint.socketPath }
211
+ : { host: endpoint.host, port: endpoint.port };
212
+ const Redis = load('ioredis').Redis;
213
+ const connection = new Redis({
214
+ ...target,
215
+ username: options.username,
216
+ password: options.password,
217
+ tls: options.tls,
218
+ db: endpoint.db,
219
+ keyPrefix: undefined,
220
+ lazyConnect: true,
221
+ connectTimeout: 10000,
222
+ commandTimeout: 10000,
223
+ retryStrategy: () => null,
224
+ maxRetriesPerRequest: 0,
225
+ enableOfflineQueue: false,
226
+ });
227
+ connection.on('error', () => {});
228
+ try {
229
+ await connection.connect();
230
+ const prefix =
231
+ resource.kind === 'queue' ? resource.prefix.slice(0, -'queue:'.length) : resource.prefix;
232
+ return await hasFreshnessRedis(connection, prefix);
233
+ } finally {
234
+ connection.disconnect();
235
+ }
236
+ },
237
+ };
238
+ }
@@ -19,6 +19,7 @@ const lifecycleSource = `
19
19
  '../../types/index.ts': 'export const EnumAppEvent = { AppStartError: "error", AppStarted: "started" };',
20
20
  '../core/isolationGuard.ts': 'export function assertIsolatedTestConfig() {}',
21
21
  '../core/testClaim.ts': 'export function registerTestClaimResourceInventory() {}',
22
+ '../core/testFreshness.ts': 'export async function assertFreshTestResources() { await globalThis.phase("Freshness"); }',
22
23
  '../module/loader.ts': 'export class ModuleLoader { async execute() { await globalThis.phase("Load"); } }',
23
24
  '../utils/util.ts': 'export function deepExtend(target, source) { Object.assign(target, source); }',
24
25
  };
@@ -62,7 +63,7 @@ const lifecycleSource = `
62
63
  },
63
64
  };
64
65
  const start = new Start(app);
65
- const order = ['Config', 'Load', 'Start', 'Ready', 'Started'];
66
+ const order = ['Config', 'Freshness', 'Load', 'Start', 'Ready', 'Started'];
66
67
  if (failingPhase) {
67
68
  await assert.rejects(start.start(), error => error === failure);
68
69
  assert.equal(app.meta.appStartError, failure);
@@ -85,13 +86,13 @@ const lifecycleSource = `
85
86
  ...(failingPhase ? ['error'] : []),
86
87
  ]);
87
88
  if (reachesStarted) {
88
- assert.deepEqual(events[0].phases, ['Config', 'Load', 'Start', 'Ready']);
89
+ assert.deepEqual(events[0].phases, ['Config', 'Freshness', 'Load', 'Start', 'Ready']);
89
90
  }
90
91
  assert.equal(await readFile(lockPath, 'utf8'), lockContents);
91
92
  console.log('verified');
92
93
  `;
93
94
 
94
- for (const failingPhase of ['', 'Config', 'Load', 'Start', 'Ready', 'Started']) {
95
+ for (const failingPhase of ['', 'Config', 'Freshness', 'Load', 'Start', 'Ready', 'Started']) {
95
96
  test(`Start ignores the legacy preparation lock on ${failingPhase ? `${failingPhase} failure` : 'success'}`, async () => {
96
97
  const root = await mkdtemp(path.join(await realpath(tmpdir()), 'vona-start-'));
97
98
  try {
@@ -4,6 +4,7 @@ import type { VonaApplication } from '../core/application.ts';
4
4
  import { EnumAppEvent } from '../../types/index.ts';
5
5
  import { assertIsolatedTestConfig } from '../core/isolationGuard.ts';
6
6
  import { registerTestClaimResourceInventory } from '../core/testClaim.ts';
7
+ import { assertFreshTestResources } from '../core/testFreshness.ts';
7
8
  import { ModuleLoader } from '../module/loader.ts';
8
9
  import { deepExtend } from '../utils/util.ts';
9
10
 
@@ -18,6 +19,7 @@ export class Start {
18
19
  const app = this.app;
19
20
  try {
20
21
  await this._start_appConfig();
22
+ await assertFreshTestResources(app);
21
23
  await this._start_appLoad();
22
24
  await this._start_appStart();
23
25
  await this._start_appReady();
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-mock",
3
- "version": "6.1.47",
3
+ "version": "6.1.48",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona mock",
6
6
  "keywords": [