cabloy 5.1.205 → 5.1.207

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 (34) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +24 -0
  3. package/package.json +1 -1
  4. package/repo-agent-governance/managed-assets.json +3 -3
  5. package/repo-agent-governance/policies/repository.md +1 -1
  6. package/repo-docs/backend/migration-and-changes.md +27 -8
  7. package/repo-docs/reference/repo-scripts.md +20 -1
  8. package/scripts/init.ts +15 -0
  9. package/scripts/runIsolatedTest.test.ts +21 -0
  10. package/scripts/runIsolatedTest.ts +14 -6
  11. package/vona/packages-cli/cli/package.json +1 -1
  12. package/vona/packages-cli/cli-set-api/package.json +1 -1
  13. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dev.ts +17 -2
  14. package/vona/packages-cli/cli-set-api/test/binDevLifecycle.test.ts +100 -0
  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/framework/cluster.ts +1 -1
  18. package/vona/packages-vona/vona-core/src/lib/framework/processMaster.test.ts +185 -0
  19. package/vona/packages-vona/vona-core/src/lib/framework/processMaster.ts +18 -7
  20. package/vona/packages-vona/vona-core/src/lib/framework/start.test.ts +127 -0
  21. package/vona/packages-vona/vona-mock/package.json +1 -1
  22. package/vona/pnpm-lock.yaml +1 -1
  23. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  24. package/vona/src/suite-vendor/a-vona/modules/a-redis/package.json +1 -1
  25. package/vona/src/suite-vendor/a-vona/modules/a-redis/src/service/redis.ts +77 -42
  26. package/vona/src/suite-vendor/a-vona/modules/a-redis/test/redisCleanup.test.ts +378 -0
  27. package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
  28. package/vona/src/suite-vendor/a-vona/modules/a-startup/src/service/startup.ts +56 -9
  29. package/vona/src/suite-vendor/a-vona/modules/a-startup/src/types/startup.ts +1 -0
  30. package/vona/src/suite-vendor/a-vona/modules/a-startup/test/devSeed.test.ts +104 -0
  31. package/vona/src/suite-vendor/a-vona/modules/a-version/package.json +1 -1
  32. package/vona/src/suite-vendor/a-vona/modules/a-version/src/service/database.ts +183 -51
  33. package/vona/src/suite-vendor/a-vona/modules/a-version/test/devDatabasePreparation.test.ts +221 -0
  34. package/vona/src/suite-vendor/a-vona/package.json +1 -1
@@ -1,4 +1,4 @@
1
- import type { IInstanceRecord } from 'vona';
1
+ import type { IInstanceRecord, VonaApplication } from 'vona';
2
2
  import type { ConfigInstanceBase } from 'vona-module-a-instance';
3
3
  import type { IDatabaseClientRecord, ServiceDatabaseClient } from 'vona-module-a-orm';
4
4
 
@@ -20,6 +20,26 @@ import { createDatabaseTestName, getDatabaseTestPrefix } from '../lib/databaseTe
20
20
  const __separator = '-';
21
21
  const __timeFormat = `yyyyMMdd${__separator}HHmmss`;
22
22
 
23
+ interface IDevDatabaseTarget {
24
+ clientName: keyof IDatabaseClientRecord;
25
+ instanceName?: keyof IInstanceRecord;
26
+ configInstanceBase?: ConfigInstanceBase;
27
+ }
28
+
29
+ interface IDevDatabasePlan extends IDevDatabaseTarget {
30
+ databasePrefix: string;
31
+ sqliteRoot?: string;
32
+ databases: string[];
33
+ create: boolean;
34
+ }
35
+
36
+ interface IDevDatabasePreparation {
37
+ prepared: boolean;
38
+ createdInstances: Set<keyof IInstanceRecord>;
39
+ }
40
+
41
+ const devDatabasePreparations = new WeakMap<VonaApplication, IDevDatabasePreparation>();
42
+
23
43
  export function assertDisposableDatabaseName(
24
44
  databaseName: string,
25
45
  databasePrefix: string,
@@ -49,6 +69,110 @@ export class ServiceDatabase extends BeanBase {
49
69
  return getDatabaseTestPrefix(this.app.name, scope);
50
70
  }
51
71
 
72
+ get createdInstanceNames(): (keyof IInstanceRecord)[] {
73
+ return [...(devDatabasePreparations.get(this.app)?.createdInstances ?? [])];
74
+ }
75
+
76
+ consumeCreatedInstance(instanceName: keyof IInstanceRecord) {
77
+ devDatabasePreparations.get(this.app)?.createdInstances.delete(instanceName);
78
+ }
79
+
80
+ public async prepareDevDatabases(reset = process.env.VONA_DB_RESET === 'true') {
81
+ if (!this.app.meta.isDev) return;
82
+ if (!this.configDatabase.testDatabase) {
83
+ if (reset) {
84
+ throw new Error('db:reset requires database.testDatabase=true in the dev configuration');
85
+ }
86
+ return;
87
+ }
88
+ if (devDatabasePreparations.get(this.app)?.prepared) return;
89
+
90
+ if (typeof this.configDatabase.defaultClient !== 'string') {
91
+ throw new TypeError('Disposable dev database preparation requires a static default client');
92
+ }
93
+ // Validate every target before clearing any application resources.
94
+ const plans: IDevDatabasePlan[] = [];
95
+ const clientNames = new Set<keyof IDatabaseClientRecord>();
96
+ for (const target of this.__databaseTargets()) {
97
+ if (target.configInstanceBase?.isolate && !target.configInstanceBase.id) {
98
+ throw new Error(`should specify id for isolate instance: ${target.instanceName}`);
99
+ }
100
+ const clientName = this.$scope.orm.service.database.prepareClientNameReal(target.clientName);
101
+ if (clientNames.has(clientName)) {
102
+ throw new Error(`Disposable database targets must use distinct clients: ${clientName}`);
103
+ }
104
+ clientNames.add(clientName);
105
+ plans.push(
106
+ await this.bean.database.switchDb(async () => {
107
+ const client = this.bean.database.current.client;
108
+ const databasePrefix = this.getDatabasePrefix(
109
+ target.instanceName,
110
+ target.configInstanceBase,
111
+ );
112
+ const sqliteRoot = this.__sqliteRoot(client);
113
+ const currentName = client.getDatabaseName();
114
+ const currentFilename = sqliteRoot ? path.basename(currentName, '.db') : currentName;
115
+ if (currentFilename.startsWith(databasePrefix)) {
116
+ assertDisposableDatabaseName(currentName, databasePrefix, sqliteRoot);
117
+ if (reset) {
118
+ throw new Error('db:reset requires a connection outside the disposable database');
119
+ }
120
+ }
121
+ const dbs = await this.__fetchDatabases(
122
+ client,
123
+ target.instanceName,
124
+ target.configInstanceBase,
125
+ );
126
+ for (const db of dbs) {
127
+ assertDisposableDatabaseName(db.name, databasePrefix, sqliteRoot);
128
+ }
129
+ return {
130
+ ...target,
131
+ databasePrefix,
132
+ sqliteRoot,
133
+ databases: dbs.map(db => db.name),
134
+ create: reset,
135
+ };
136
+ }, target.clientName),
137
+ );
138
+ }
139
+
140
+ if (!reset) {
141
+ const missing = plans.find(plan => plan.databases.length === 0);
142
+ if (missing) this.__missingDevDatabase(missing.instanceName);
143
+ }
144
+
145
+ if (reset) {
146
+ await this.$scope.redis.service.redis.clearAllData();
147
+ await this.bean.worker.setAlive();
148
+ }
149
+ const preparation: IDevDatabasePreparation = {
150
+ prepared: false,
151
+ createdInstances: new Set(),
152
+ };
153
+ for (const plan of plans) {
154
+ await this.bean.database.switchDb(async () => {
155
+ const client = this.bean.database.current.client;
156
+ if (reset) {
157
+ for (const databaseName of plan.databases) {
158
+ assertDisposableDatabaseName(databaseName, plan.databasePrefix, plan.sqliteRoot);
159
+ await client.connection.schema.dropDatabase(databaseName);
160
+ }
161
+ }
162
+ const databaseName = plan.create
163
+ ? await this.__createDatabase(client, plan.instanceName, plan.configInstanceBase)
164
+ : plan.databases[0];
165
+ assertDisposableDatabaseName(databaseName, plan.databasePrefix, plan.sqliteRoot);
166
+ await client.changeConfigConnectionAndReloadWorker(databaseName);
167
+ if (plan.create) preparation.createdInstances.add(plan.instanceName ?? '');
168
+ this.$logger.silly(chalk.cyan(`dialect: ${client.db.dialectName}`));
169
+ this.$logger.silly(chalk.cyan(`database: ${databaseName}, pid: ${process.pid}`));
170
+ }, plan.clientName);
171
+ }
172
+ preparation.prepared = true;
173
+ devDatabasePreparations.set(this.app, preparation);
174
+ }
175
+
52
176
  public async databaseInitStartup() {
53
177
  // database
54
178
  await this.__prepareDatabases(true);
@@ -97,22 +221,41 @@ export class ServiceDatabase extends BeanBase {
97
221
  return await client.connection.schema.createDatabase(databaseName);
98
222
  }
99
223
 
100
- private async __prepareDatabases(versionStart: boolean) {
101
- // default
102
- await this.prepareDatabase('default', versionStart);
103
- // isolate
224
+ private __databaseTargets(): IDevDatabaseTarget[] {
225
+ const targets: IDevDatabaseTarget[] = [{ clientName: 'default' }];
104
226
  for (const key in this.app.config.instance.instances) {
105
227
  const instanceName = key as keyof IInstanceRecord;
106
228
  const configInstanceBase = this.app.config.instance.instances[instanceName];
107
- if (configInstanceBase === false) continue;
108
- if (!configInstanceBase.isolate) continue;
109
- if (!configInstanceBase.isolateClient)
229
+ if (configInstanceBase === false || !configInstanceBase.isolate) continue;
230
+ if (!configInstanceBase.isolateClient) {
110
231
  throw new Error(`should specify isolateClient for isolate instance: ${instanceName}`);
111
- await this.prepareDatabase(
112
- configInstanceBase.isolateClient,
113
- versionStart,
232
+ }
233
+ targets.push({
234
+ clientName: configInstanceBase.isolateClient,
114
235
  instanceName,
115
236
  configInstanceBase,
237
+ });
238
+ }
239
+ return targets;
240
+ }
241
+
242
+ private __sqliteRoot(client: ServiceDatabaseClient) {
243
+ return client.db.dialectName === 'better-sqlite3'
244
+ ? resolveVonaLocalPaths(
245
+ this.app.projectPath,
246
+ this.app.meta.env.META_MODE,
247
+ this.app.meta.env.VONA_TEST_RUN_ID,
248
+ ).sqliteRoot
249
+ : undefined;
250
+ }
251
+
252
+ private async __prepareDatabases(versionStart: boolean) {
253
+ for (const target of this.__databaseTargets()) {
254
+ await this.prepareDatabase(
255
+ target.clientName,
256
+ versionStart,
257
+ target.instanceName,
258
+ target.configInstanceBase,
116
259
  );
117
260
  }
118
261
  }
@@ -122,14 +265,36 @@ export class ServiceDatabase extends BeanBase {
122
265
  instanceName?: keyof IInstanceRecord,
123
266
  configInstanceBase?: ConfigInstanceBase,
124
267
  ) {
125
- await this.__prepareDatabaseInner(versionStart, instanceName, configInstanceBase);
268
+ await this.__prepareDatabaseInner(instanceName, configInstanceBase);
126
269
  if (versionStart) {
127
270
  await this.scope.service.version.__start();
128
271
  }
129
272
  }
130
273
 
274
+ private __missingDevDatabase(instanceName?: keyof IInstanceRecord): never {
275
+ // eslint-disable-next-line
276
+ console.error(
277
+ [
278
+ '',
279
+ chalk.bgRed.white.bold(' DEVELOPMENT DATABASE MISSING '),
280
+ chalk.yellow(`Instance: ${instanceName || 'default'}`),
281
+ '',
282
+ chalk.bold.yellow('Run this command before starting the development server:'),
283
+ '',
284
+ chalk.bold.cyan(' npm run db:reset'),
285
+ '',
286
+ chalk.yellow('Warning: db:reset deletes managed development data.'),
287
+ chalk.yellow('Stop processes using the same database and Redis namespace first.'),
288
+ '',
289
+ ].join('\n'),
290
+ );
291
+ throw new Error(
292
+ `Development database is missing for instance "${instanceName || 'default'}". ` +
293
+ 'Run "npm run db:reset" before starting the development server.',
294
+ );
295
+ }
296
+
131
297
  private async __prepareDatabaseInner(
132
- versionStart: boolean,
133
298
  instanceName?: keyof IInstanceRecord,
134
299
  configInstanceBase?: ConfigInstanceBase,
135
300
  ) {
@@ -137,6 +302,10 @@ export class ServiceDatabase extends BeanBase {
137
302
  // donothing
138
303
  return;
139
304
  }
305
+ if (this.app.meta.isDev) {
306
+ await this.prepareDevDatabases();
307
+ return;
308
+ }
140
309
  const databasePrefix = this.getDatabasePrefix(instanceName, configInstanceBase);
141
310
  if (this.app.meta.env.VONA_TEST_RUN_ID) {
142
311
  const paths = resolveVonaLocalPaths(
@@ -149,16 +318,7 @@ export class ServiceDatabase extends BeanBase {
149
318
  // client
150
319
  const client = this.bean.database.current.client;
151
320
  // get current database name
152
- let databaseName = client.getDatabaseName();
153
- // dev/debug db
154
- if (this.app.meta.isDev) {
155
- const reset = versionStart && process.env.VONA_DB_RESET === 'true';
156
- // Never drop a configured, non-disposable development database.
157
- if (reset && !this.configDatabase.testDatabase) {
158
- throw new Error('db:reset requires database.testDatabase=true in the dev configuration');
159
- }
160
- if (!this.configDatabase.testDatabase) return;
161
- }
321
+ const databaseName = client.getDatabaseName();
162
322
  const sqliteRoot =
163
323
  client.db.dialectName === 'better-sqlite3'
164
324
  ? resolveVonaLocalPaths(
@@ -180,34 +340,6 @@ export class ServiceDatabase extends BeanBase {
180
340
  `Isolated test database is outside its disposable namespace: ${databaseName}`,
181
341
  );
182
342
  }
183
- // dev/debug db
184
- if (this.app.meta.isDev) {
185
- const reset = versionStart && process.env.VONA_DB_RESET === 'true';
186
- // The database used to enumerate/drop databases must not itself be dropped.
187
- if (isTestDatabase) {
188
- if (reset)
189
- throw new Error('db:reset requires a connection outside the disposable database');
190
- return;
191
- }
192
- const dbs = await this.__fetchDatabases(client, instanceName, configInstanceBase);
193
- for (const db of dbs) {
194
- assertDisposableDatabaseName(db.name, databasePrefix, sqliteRoot);
195
- }
196
- if (reset) {
197
- for (const db of dbs) {
198
- await client.connection.schema.dropDatabase(db.name);
199
- }
200
- }
201
- databaseName =
202
- reset || dbs.length === 0
203
- ? await this.__createDatabase(client, instanceName, configInstanceBase)
204
- : dbs[0].name;
205
- assertDisposableDatabaseName(databaseName, databasePrefix, sqliteRoot);
206
- // set config and reload client
207
- await client.changeConfigConnectionAndReloadWorker(databaseName);
208
- this.$logger.silly(chalk.cyan(`dialect: ${client.db.dialectName}`));
209
- this.$logger.silly(chalk.cyan(`database: ${databaseName}, pid: ${process.pid}`));
210
- }
211
343
  // test db
212
344
  if (this.app.meta.isTest) {
213
345
  // check
@@ -0,0 +1,221 @@
1
+ import assert from 'node:assert/strict';
2
+ import { describe, it } from 'node:test';
3
+
4
+ import { ServiceDatabase } from '../src/service/database.ts';
5
+
6
+ const databaseName = 'vona-test-preparation-s-20261014-120000';
7
+ const isolateName = 'vona-test-preparation-i-isolateTest-20261014-120000';
8
+
9
+ function fixture(existing: string[] = [], isolate?: string[]) {
10
+ const calls: string[] = [];
11
+ const app = {
12
+ name: 'preparation',
13
+ meta: { isDev: true, isTest: false, env: { META_MODE: 'dev' } },
14
+ config: {
15
+ database: { testDatabase: true, defaultClient: 'main', clients: { main: {}, isolated: {} } },
16
+ instance: {
17
+ instances: isolate
18
+ ? { isolateTest: { id: 2, isolate: true, isolateClient: 'isolated' } }
19
+ : {},
20
+ },
21
+ },
22
+ };
23
+ const names = { main: existing, isolated: isolate ?? [] };
24
+ const clients = Object.fromEntries(
25
+ Object.entries(names).map(([key, databases]) => [
26
+ key,
27
+ {
28
+ db: { dialectName: 'pg' },
29
+ getDatabaseName: (): string => 'postgres',
30
+ connection: {
31
+ schema: {
32
+ fetchDatabases: async () => databases.map(name => ({ name })),
33
+ dropDatabase: async (name: string) => {
34
+ calls.push(`drop:${name}`);
35
+ databases.splice(databases.indexOf(name), 1);
36
+ },
37
+ createDatabase: async (name: string) => {
38
+ calls.push(`create:${key}`);
39
+ databases.push(name);
40
+ return name;
41
+ },
42
+ },
43
+ },
44
+ changeConfigConnectionAndReloadWorker: async (name: string) => calls.push(`reload:${name}`),
45
+ },
46
+ ]),
47
+ );
48
+ const database = {
49
+ current: { client: clients.main },
50
+ switchDb: async (fn: () => Promise<unknown>, clientName: string) => {
51
+ const previous = database.current;
52
+ database.current = { client: clients[clientName === 'default' ? 'main' : clientName] };
53
+ try {
54
+ return await fn();
55
+ } finally {
56
+ database.current = previous;
57
+ }
58
+ },
59
+ };
60
+ const service = Object.create(ServiceDatabase.prototype) as ServiceDatabase;
61
+ Object.defineProperties(service, {
62
+ app: { value: app },
63
+ bean: { value: { database, worker: { setAlive: async () => calls.push('alive') } } },
64
+ $scope: {
65
+ value: {
66
+ orm: {
67
+ service: {
68
+ database: {
69
+ prepareClientNameReal: (name: string) => (name === 'default' ? 'main' : name),
70
+ },
71
+ },
72
+ },
73
+ redis: { service: { redis: { clearAllData: async () => calls.push('clear') } } },
74
+ version: { service: { version: { __start: async () => calls.push('migrate') } } },
75
+ },
76
+ },
77
+ scope: { value: { service: { version: { __start: async () => calls.push('migrate') } } } },
78
+ $logger: { value: { silly() {} } },
79
+ });
80
+ return { service, app, calls, clients };
81
+ }
82
+
83
+ describe('dev database preparation', { concurrency: false }, () => {
84
+ it('clears all resources before reset creation and records only actual creations', async () => {
85
+ const { service, calls } = fixture();
86
+ await service.prepareDevDatabases(true);
87
+ assert.deepEqual(calls.slice(0, 3), ['clear', 'alive', 'create:main']);
88
+ assert.deepEqual(service.createdInstanceNames, ['']);
89
+ await service.prepareDevDatabases();
90
+ await service.databaseInitStartup();
91
+ assert.equal(calls.filter(call => call === 'clear').length, 1);
92
+ assert.equal(calls.filter(call => call === 'create:main').length, 1);
93
+ assert.equal(calls.at(-1), 'migrate');
94
+ service.consumeCreatedInstance('');
95
+ assert.deepEqual(service.createdInstanceNames, []);
96
+ });
97
+
98
+ it('reuses an existing database without clearing or requesting seeds', async () => {
99
+ const { service, calls } = fixture([databaseName]);
100
+ await service.prepareDevDatabases();
101
+ assert.deepEqual(calls, [`reload:${databaseName}`]);
102
+ assert.deepEqual(service.createdInstanceNames, []);
103
+ });
104
+
105
+ it('rejects a missing isolate before reloading or changing the existing shared database', async () => {
106
+ const { service, calls } = fixture([databaseName], []);
107
+ await assert.rejects(service.prepareDevDatabases(false), /isolateTest.*npm run db:reset/);
108
+ assert.deepEqual(calls, []);
109
+ assert.deepEqual(service.createdInstanceNames, []);
110
+ });
111
+
112
+ it('rejects a missing shared database without any preparation side effects', async () => {
113
+ const { service, calls } = fixture();
114
+ await assert.rejects(service.prepareDevDatabases(false), /default.*npm run db:reset/);
115
+ assert.deepEqual(calls, []);
116
+ assert.deepEqual(service.createdInstanceNames, []);
117
+ });
118
+
119
+ it('prints a prominent reset command and destructive-data warning before rejecting', async t => {
120
+ const { service, calls } = fixture();
121
+ const output: string[] = [];
122
+ t.mock.method(console, 'error', (message: string) => output.push(message));
123
+ await assert.rejects(service.prepareDevDatabases(false), /default.*npm run db:reset/);
124
+ assert.equal(output.length, 1);
125
+ assert.match(output[0], /DEVELOPMENT DATABASE MISSING/);
126
+ assert.match(output[0], /Instance: default/);
127
+ assert.match(output[0], /npm run db:reset/);
128
+ assert.match(output[0], /deletes managed development data/);
129
+ assert.match(output[0], /Stop processes using the same database and Redis namespace first/);
130
+ assert.deepEqual(calls, []);
131
+ });
132
+
133
+ it('rejects missing targets through legacy startup entrypoints before migration', async () => {
134
+ for (const versionStart of [false, true]) {
135
+ const { service, calls } = fixture();
136
+ await assert.rejects(
137
+ service.prepareDatabase('main' as never, versionStart),
138
+ /npm run db:reset/,
139
+ );
140
+ assert.deepEqual(calls, []);
141
+ }
142
+ });
143
+
144
+ it('preflights every target before any deletion', async () => {
145
+ const { service, calls } = fixture([databaseName], ['unowned-database']);
146
+ await assert.rejects(service.prepareDevDatabases());
147
+ assert.deepEqual(calls, []);
148
+ });
149
+
150
+ it('stops if full resource cleanup fails', async () => {
151
+ const { service, calls } = fixture();
152
+ (service as any).$scope.redis.service.redis.clearAllData = async () => {
153
+ throw new Error('cleanup failed');
154
+ };
155
+ await assert.rejects(service.prepareDevDatabases(true), /cleanup failed/);
156
+ assert.deepEqual(calls, []);
157
+ assert.deepEqual(service.createdInstanceNames, []);
158
+ });
159
+
160
+ it('clears once and recreates all reset targets before migration', async () => {
161
+ const { service, calls } = fixture([databaseName], [isolateName]);
162
+ await service.prepareDevDatabases(true);
163
+ assert.equal(calls.filter(call => call === 'clear').length, 1);
164
+ assert.ok(calls.indexOf('clear') < calls.indexOf(`drop:${databaseName}`));
165
+ assert.ok(calls.includes(`drop:${isolateName}`));
166
+ assert.deepEqual(service.createdInstanceNames, ['', 'isolateTest']);
167
+ await service.databaseInitStartup();
168
+ assert.equal(calls.filter(call => call.startsWith('drop:')).length, 2);
169
+ assert.equal(calls.filter(call => call.startsWith('create:')).length, 2);
170
+ assert.equal(calls.filter(call => call === 'migrate').length, 2);
171
+ });
172
+
173
+ it('rejects non-disposable reset without clearing anything', async () => {
174
+ const { service, app, calls } = fixture([databaseName]);
175
+ app.config.database.testDatabase = false;
176
+ await assert.rejects(service.prepareDevDatabases(true), /testDatabase=true/);
177
+ assert.deepEqual(calls, []);
178
+ await service.prepareDevDatabases(false);
179
+ assert.deepEqual(calls, []);
180
+ });
181
+
182
+ it('refuses to reset through a connection inside the target namespace', async () => {
183
+ const { service, clients, calls } = fixture([databaseName]);
184
+ clients.main.getDatabaseName = () => databaseName;
185
+ await assert.rejects(service.prepareDevDatabases(true), /connection outside/);
186
+ assert.deepEqual(calls, []);
187
+ });
188
+
189
+ it('checks actual existence even if the configured name belongs to the managed namespace', async () => {
190
+ const { service, clients, calls } = fixture();
191
+ clients.main.getDatabaseName = () => databaseName;
192
+ await assert.rejects(service.prepareDevDatabases(false), /npm run db:reset/);
193
+ assert.deepEqual(calls, []);
194
+ });
195
+
196
+ it('propagates discovery failures without treating them as missing databases', async () => {
197
+ const { service, clients, calls } = fixture([databaseName]);
198
+ clients.main.connection.schema.fetchDatabases = async () => {
199
+ throw new Error('discovery failed');
200
+ };
201
+ await assert.rejects(service.prepareDevDatabases(false), /discovery failed/);
202
+ assert.deepEqual(calls, []);
203
+ });
204
+
205
+ it('propagates reset creation failure without migrating or requesting seeds', async () => {
206
+ const { service, clients, calls } = fixture([databaseName]);
207
+ clients.main.connection.schema.createDatabase = async () => {
208
+ throw new Error('creation failed');
209
+ };
210
+ await assert.rejects(service.prepareDevDatabases(true), /creation failed/);
211
+ assert.deepEqual(calls, ['clear', 'alive', `drop:${databaseName}`]);
212
+ assert.deepEqual(service.createdInstanceNames, []);
213
+ });
214
+
215
+ it('preserves both existing targets on reuse', async () => {
216
+ const { service, calls } = fixture([databaseName], [isolateName]);
217
+ await service.prepareDevDatabases();
218
+ assert.deepEqual(calls, [`reload:${databaseName}`, `reload:${isolateName}`]);
219
+ assert.deepEqual(service.createdInstanceNames, []);
220
+ });
221
+ });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-suite-a-vona",
3
- "version": "5.1.96",
3
+ "version": "5.1.97",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "author": "",