cabloy 5.1.206 → 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 (31) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +14 -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/vona/packages-cli/cli/package.json +1 -1
  10. package/vona/packages-cli/cli-set-api/package.json +1 -1
  11. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dev.ts +17 -2
  12. package/vona/packages-cli/cli-set-api/test/binDevLifecycle.test.ts +100 -0
  13. package/vona/packages-vona/vona/package.json +1 -1
  14. package/vona/packages-vona/vona-core/package.json +1 -1
  15. package/vona/packages-vona/vona-core/src/lib/framework/cluster.ts +1 -1
  16. package/vona/packages-vona/vona-core/src/lib/framework/processMaster.test.ts +185 -0
  17. package/vona/packages-vona/vona-core/src/lib/framework/processMaster.ts +18 -7
  18. package/vona/packages-vona/vona-core/src/lib/framework/start.test.ts +127 -0
  19. package/vona/packages-vona/vona-mock/package.json +1 -1
  20. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  21. package/vona/src/suite-vendor/a-vona/modules/a-redis/package.json +1 -1
  22. package/vona/src/suite-vendor/a-vona/modules/a-redis/src/service/redis.ts +77 -42
  23. package/vona/src/suite-vendor/a-vona/modules/a-redis/test/redisCleanup.test.ts +378 -0
  24. package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
  25. package/vona/src/suite-vendor/a-vona/modules/a-startup/src/service/startup.ts +56 -9
  26. package/vona/src/suite-vendor/a-vona/modules/a-startup/src/types/startup.ts +1 -0
  27. package/vona/src/suite-vendor/a-vona/modules/a-startup/test/devSeed.test.ts +104 -0
  28. package/vona/src/suite-vendor/a-vona/modules/a-version/package.json +1 -1
  29. package/vona/src/suite-vendor/a-vona/modules/a-version/src/service/database.ts +183 -51
  30. package/vona/src/suite-vendor/a-vona/modules/a-version/test/devDatabasePreparation.test.ts +221 -0
  31. package/vona/src/suite-vendor/a-vona/package.json +1 -1
@@ -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": "",