cabloy 5.1.203 → 5.1.205

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 (127) hide show
  1. package/.cabloy-version +1 -1
  2. package/.gitignore +2 -0
  3. package/CHANGELOG.md +21 -0
  4. package/lint-staged.config.mjs +2 -0
  5. package/oxfmt.config.ts +2 -0
  6. package/oxlint.config.ts +2 -0
  7. package/package.json +6 -5
  8. package/repo-agent-governance/managed-assets.json +9 -9
  9. package/repo-agent-governance/policies/repository.md +5 -2
  10. package/repo-agent-governance/skills/cabloy-backend-scaffold/references/follow-up-checklist.md +2 -2
  11. package/repo-agent-governance/skills/cabloy-workflow/SKILL.md +2 -0
  12. package/repo-docs/backend/migration-and-changes.md +13 -12
  13. package/repo-docs/backend/orm-select-guide.md +6 -0
  14. package/repo-docs/backend/runtime-and-flavors.md +2 -1
  15. package/repo-docs/backend/transaction-guide.md +12 -0
  16. package/repo-docs/backend/unit-testing.md +12 -8
  17. package/repo-docs/blogs/ai-coding-one-prompt-crud-ssr-multitasking/cover-en-v1.png +0 -0
  18. package/repo-docs/blogs/ai-coding-one-prompt-crud-ssr-multitasking/images/completion-summary.jpg +0 -0
  19. package/repo-docs/blogs/ai-coding-one-prompt-crud-ssr-multitasking/images/multitasking-draft.jpg +0 -0
  20. package/repo-docs/blogs/ai-coding-one-prompt-crud-ssr-multitasking/images/ssr-refresh.jpg +0 -0
  21. package/repo-docs/blogs/ai-coding-one-prompt-crud-ssr-multitasking/index.md +216 -0
  22. package/repo-docs/blogs/index.md +15 -0
  23. package/repo-docs/frontend/scripts.md +1 -1
  24. package/repo-docs/fullstack/parallel-worktree-environment.md +6 -6
  25. package/repo-docs/fullstack/ssr-entry-modes.md +19 -0
  26. package/repo-docs/index.md +2 -0
  27. package/repo-docs/reference/repo-scripts.md +22 -1
  28. package/repo-e2e/config/playwright.config.ts +32 -14
  29. package/repo-e2e/docs/specs/blogs-index.spec.ts +23 -6
  30. package/repo-e2e/scripts/e2e.test.ts +39 -0
  31. package/repo-e2e/scripts/e2e.ts +19 -1
  32. package/repo-e2e/scripts/managedTest.test.ts +389 -0
  33. package/repo-e2e/scripts/managedTest.ts +210 -0
  34. package/repo-e2e/scripts/managedTestImports.test.ts +74 -0
  35. package/repo-e2e/scripts/managedTestImports.ts +110 -0
  36. package/repo-e2e/scripts/runE2e.ts +12 -1
  37. package/repo-e2e/scripts/startE2eVona.ts +37 -5
  38. package/repo-e2e/specs/a-commerce.spec.ts +1 -1
  39. package/repo-e2e/specs/cabloy-basic.spec.ts +1 -1
  40. package/repo-e2e/specs/controller-boundary.spec.ts +1 -1
  41. package/repo-e2e/specs/home-user-account.spec.ts +2 -1
  42. package/repo-e2e/specs/markdown.spec.ts +1 -1
  43. package/repo-e2e/specs/routed-dialog.spec.ts +1 -1
  44. package/scripts/runIsolatedTest.test.ts +426 -0
  45. package/scripts/runIsolatedTest.ts +454 -0
  46. package/scripts/testClaimCleanup.test.ts +346 -0
  47. package/scripts/testClaimCleanup.ts +375 -0
  48. package/scripts/testClaimProcesses.test.ts +106 -0
  49. package/scripts/testClaimProcesses.ts +129 -0
  50. package/scripts/testProcessGuardian.ts +163 -0
  51. package/scripts/upgrade.ts +1 -16
  52. package/vona/oxfmt.config.ts +2 -0
  53. package/vona/oxlint.config.ts +2 -0
  54. package/vona/package.original.json +2 -2
  55. package/vona/packages-cli/cli/package.json +1 -1
  56. package/vona/packages-cli/cli/src/play.test.ts +37 -0
  57. package/vona/packages-cli/cli/src/play.ts +23 -1
  58. package/vona/packages-cli/cli-set-api/package.json +1 -1
  59. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dbReset.ts +16 -7
  60. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.dev.ts +13 -7
  61. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.play.ts +5 -1
  62. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.bin.test.ts +12 -7
  63. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/configUtils.ts +21 -2
  64. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/generateVonaMeta.ts +10 -0
  65. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/resolveTestCleanupConfig.test.ts +153 -0
  66. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/resolveTestCleanupConfig.ts +141 -0
  67. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/testCleanupAdapters.test.ts +148 -0
  68. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/testCleanupAdapters.ts +127 -0
  69. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/testCleanupProcess.test.ts +47 -0
  70. package/vona/packages-cli/cli-set-api/src/lib/bean/toolsBin/testCleanupProcess.ts +139 -0
  71. package/vona/packages-cli/cli-set-api/src/lib/utils.ts +2 -3
  72. package/vona/packages-cli/cli-set-api/templates/config/_tsconfig_project.json +1 -0
  73. package/vona/packages-cli/cli-set-api/templates/config/_tsconfig_source.json +1 -0
  74. package/vona/packages-cli/cli-set-api/test/binDevMode.test.ts +18 -0
  75. package/vona/packages-cli/cli-set-api/toolsIsolate/dbReset.ts +15 -2
  76. package/vona/packages-cli/cli-set-api/tsconfig.build.json +2 -1
  77. package/vona/packages-utils/babel-plugin-vona-bean-module/package.json +1 -1
  78. package/vona/packages-utils/babel-plugin-vona-bean-module/src/index.test.ts +16 -0
  79. package/vona/packages-utils/babel-plugin-vona-bean-module/src/index.ts +3 -2
  80. package/vona/packages-vona/vona/package.json +1 -1
  81. package/vona/packages-vona/vona-core/package.json +1 -1
  82. package/vona/packages-vona/vona-core/src/lib/addon/sqlite3.ts +1 -1
  83. package/vona/packages-vona/vona-core/src/lib/core/config.ts +44 -6
  84. package/vona/packages-vona/vona-core/src/lib/core/configCleanup.test.ts +40 -0
  85. package/vona/packages-vona/vona-core/src/lib/core/index.ts +3 -0
  86. package/vona/packages-vona/vona-core/src/lib/core/isolationGuard.test.ts +301 -0
  87. package/vona/packages-vona/vona-core/src/lib/core/isolationGuard.ts +312 -0
  88. package/vona/packages-vona/vona-core/src/lib/core/testClaim.test.ts +577 -0
  89. package/vona/packages-vona/vona-core/src/lib/core/testClaim.ts +985 -0
  90. package/vona/packages-vona/vona-core/src/lib/core/testIsolation.ts +129 -0
  91. package/vona/packages-vona/vona-core/src/lib/framework/createApp.ts +2 -0
  92. package/vona/packages-vona/vona-core/src/lib/framework/start.ts +4 -0
  93. package/vona/packages-vona/vona-core/src/lib/framework/useApp.ts +5 -1
  94. package/vona/packages-vona/vona-core/src/types/application/app.ts +2 -0
  95. package/vona/packages-vona/vona-core/src/types/utils/env.ts +1 -0
  96. package/vona/packages-vona/vona-mock/package.json +1 -1
  97. package/vona/pnpm-lock.yaml +40 -40
  98. package/vona/src/suite/a-commerce/modules/commerce-payment/test/paymentIndexes.test.ts +73 -10
  99. package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
  100. package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
  101. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  102. package/vona/src/suite-vendor/a-vona/modules/a-logger/package.json +1 -1
  103. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/bean/middlewareSystem.httpLog.ts +15 -1
  104. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactIsolatedReadyHeader.test.ts +24 -0
  105. package/vona/src/suite-vendor/a-vona/modules/a-logger/src/lib/redactIsolatedReadyHeader.ts +11 -0
  106. package/vona/src/suite-vendor/a-vona/modules/a-metrics/package.json +1 -1
  107. package/vona/src/suite-vendor/a-vona/modules/a-metrics/src/bean/middlewareSystem.health.ts +15 -0
  108. package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
  109. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/extend/schemaBuilder.ts +11 -4
  110. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/databaseClient_.ts +6 -1
  111. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/package.json +1 -1
  112. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/bean/databaseDialect.betterSqlite3.ts +41 -1
  113. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/bean/databaseDialect.mysql.ts +4 -1
  114. package/vona/src/suite-vendor/a-vona/modules/a-ormdialect/src/bean/databaseDialect.pg.ts +2 -1
  115. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  116. package/vona/src/suite-vendor/a-vona/modules/a-redis/package.json +1 -1
  117. package/vona/src/suite-vendor/a-vona/modules/a-redis/src/lib/redis.ts +8 -0
  118. package/vona/src/suite-vendor/a-vona/modules/a-redis/src/service/redis.ts +23 -6
  119. package/vona/src/suite-vendor/a-vona/modules/a-redis/src/service/redisClient_.ts +18 -1
  120. package/vona/src/suite-vendor/a-vona/modules/a-startup/package.json +1 -1
  121. package/vona/src/suite-vendor/a-vona/modules/a-startup/src/service/startup.ts +43 -15
  122. package/vona/src/suite-vendor/a-vona/modules/a-version/package.json +1 -1
  123. package/vona/src/suite-vendor/a-vona/modules/a-version/src/lib/databaseTestName.ts +3 -37
  124. package/vona/src/suite-vendor/a-vona/modules/a-version/src/service/database.ts +93 -15
  125. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  126. package/vona/tsconfig.json +1 -0
  127. package/scripts/initTestData.ts +0 -25
@@ -4,7 +4,15 @@ import type { IDatabaseClientRecord, ServiceDatabaseClient } from 'vona-module-a
4
4
 
5
5
  import chalk from 'chalk';
6
6
  import { DateTime } from 'luxon';
7
- import { BeanBase } from 'vona';
7
+ import path from 'node:path';
8
+ import {
9
+ BeanBase,
10
+ assertDisposableDatabaseName as assertDisposableDatabaseNameCore,
11
+ assertIsolatedTestPath,
12
+ assertTestRunOwner,
13
+ resolveVonaLocalPaths,
14
+ withIsolatedTestDatabaseDiscovery,
15
+ } from 'vona';
8
16
  import { Service } from 'vona-module-a-bean';
9
17
 
10
18
  import { createDatabaseTestName, getDatabaseTestPrefix } from '../lib/databaseTestName.ts';
@@ -12,6 +20,21 @@ import { createDatabaseTestName, getDatabaseTestPrefix } from '../lib/databaseTe
12
20
  const __separator = '-';
13
21
  const __timeFormat = `yyyyMMdd${__separator}HHmmss`;
14
22
 
23
+ export function assertDisposableDatabaseName(
24
+ databaseName: string,
25
+ databasePrefix: string,
26
+ sqliteRoot?: string,
27
+ ) {
28
+ const filename = sqliteRoot ? path.basename(databaseName, '.db') : databaseName;
29
+ assertDisposableDatabaseNameCore(filename, databasePrefix);
30
+ if (sqliteRoot) {
31
+ if (!path.isAbsolute(databaseName) || path.extname(databaseName) !== '.db') {
32
+ throw new Error(`Database is outside the disposable SQLite directory: ${databaseName}`);
33
+ }
34
+ assertIsolatedTestPath(databaseName, path.join(sqliteRoot, `${filename}.db`));
35
+ }
36
+ }
37
+
15
38
  @Service()
16
39
  export class ServiceDatabase extends BeanBase {
17
40
  get configDatabase() {
@@ -42,9 +65,14 @@ export class ServiceDatabase extends BeanBase {
42
65
  instanceName?: keyof IInstanceRecord,
43
66
  configInstanceBase?: ConfigInstanceBase,
44
67
  ) {
45
- await this.bean.database.switchDb(async () => {
46
- await this.__prepareDatabase(versionStart, instanceName, configInstanceBase);
47
- }, clientName);
68
+ const clientNameReal =
69
+ clientName === 'default' ? (this.configDatabase.defaultClient as string) : clientName;
70
+ const clientConfig = this.configDatabase.clients[clientNameReal];
71
+ await withIsolatedTestDatabaseDiscovery(this.app, clientNameReal, clientConfig, async () => {
72
+ await this.bean.database.switchDb(async () => {
73
+ await this.__prepareDatabase(versionStart, instanceName, configInstanceBase);
74
+ }, clientName);
75
+ });
48
76
  }
49
77
 
50
78
  private async __fetchDatabases(
@@ -94,13 +122,14 @@ export class ServiceDatabase extends BeanBase {
94
122
  instanceName?: keyof IInstanceRecord,
95
123
  configInstanceBase?: ConfigInstanceBase,
96
124
  ) {
97
- await this.__prepareDatabaseInner(instanceName, configInstanceBase);
125
+ await this.__prepareDatabaseInner(versionStart, instanceName, configInstanceBase);
98
126
  if (versionStart) {
99
127
  await this.scope.service.version.__start();
100
128
  }
101
129
  }
102
130
 
103
131
  private async __prepareDatabaseInner(
132
+ versionStart: boolean,
104
133
  instanceName?: keyof IInstanceRecord,
105
134
  configInstanceBase?: ConfigInstanceBase,
106
135
  ) {
@@ -109,26 +138,71 @@ export class ServiceDatabase extends BeanBase {
109
138
  return;
110
139
  }
111
140
  const databasePrefix = this.getDatabasePrefix(instanceName, configInstanceBase);
141
+ if (this.app.meta.env.VONA_TEST_RUN_ID) {
142
+ const paths = resolveVonaLocalPaths(
143
+ this.app.projectPath,
144
+ this.app.meta.env.META_MODE,
145
+ this.app.meta.env.VONA_TEST_RUN_ID,
146
+ );
147
+ assertTestRunOwner(paths.dataRoot, paths.runId!);
148
+ }
112
149
  // client
113
150
  const client = this.bean.database.current.client;
114
151
  // get current database name
115
152
  let databaseName = client.getDatabaseName();
116
- const isTestDatabase = databaseName.includes(databasePrefix);
117
153
  // dev/debug db
118
154
  if (this.app.meta.isDev) {
119
- // if enable testDatabase
120
- const enableTestDatabase = this.configDatabase.testDatabase;
121
- // check
122
- if (!enableTestDatabase || isTestDatabase) {
123
- // donothing
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
+ }
162
+ const sqliteRoot =
163
+ client.db.dialectName === 'better-sqlite3'
164
+ ? resolveVonaLocalPaths(
165
+ this.app.projectPath,
166
+ this.app.meta.env.META_MODE,
167
+ this.app.meta.env.VONA_TEST_RUN_ID,
168
+ ).sqliteRoot
169
+ : undefined;
170
+ const currentFilename = sqliteRoot ? path.basename(databaseName, '.db') : databaseName;
171
+ const isTestDatabase = currentFilename.startsWith(databasePrefix);
172
+ if (isTestDatabase) assertDisposableDatabaseName(databaseName, databasePrefix, sqliteRoot);
173
+ if (
174
+ this.app.meta.isTest &&
175
+ this.app.meta.env.VONA_TEST_RUN_ID &&
176
+ currentFilename.startsWith('vona-test-') &&
177
+ !isTestDatabase
178
+ ) {
179
+ throw new Error(
180
+ `Isolated test database is outside its disposable namespace: ${databaseName}`,
181
+ );
182
+ }
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');
124
190
  return;
125
191
  }
126
192
  const dbs = await this.__fetchDatabases(client, instanceName, configInstanceBase);
127
- if (dbs.length === 0) {
128
- databaseName = await this.__createDatabase(client, instanceName, configInstanceBase);
129
- } else {
130
- databaseName = dbs[0].name;
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
+ }
131
200
  }
201
+ databaseName =
202
+ reset || dbs.length === 0
203
+ ? await this.__createDatabase(client, instanceName, configInstanceBase)
204
+ : dbs[0].name;
205
+ assertDisposableDatabaseName(databaseName, databasePrefix, sqliteRoot);
132
206
  // set config and reload client
133
207
  await client.changeConfigConnectionAndReloadWorker(databaseName);
134
208
  this.$logger.silly(chalk.cyan(`dialect: ${client.db.dialectName}`));
@@ -143,11 +217,15 @@ export class ServiceDatabase extends BeanBase {
143
217
  }
144
218
  // drop old databases
145
219
  const dbs = await this.__fetchDatabases(client, instanceName, configInstanceBase);
220
+ for (const db of dbs) {
221
+ assertDisposableDatabaseName(db.name, databasePrefix, sqliteRoot);
222
+ }
146
223
  for (const db of dbs) {
147
224
  await client.connection.schema.dropDatabase(db.name);
148
225
  }
149
226
  // create database
150
227
  const databaseName = await this.__createDatabase(client, instanceName, configInstanceBase);
228
+ assertDisposableDatabaseName(databaseName, databasePrefix, sqliteRoot);
151
229
  // set config and reload client
152
230
  await client.changeConfigConnectionAndReloadWorker(databaseName);
153
231
  // database ready
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-suite-a-vona",
3
- "version": "5.1.94",
3
+ "version": "5.1.96",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "author": "",
@@ -31,6 +31,7 @@
31
31
  ],
32
32
  "include": [
33
33
  ".vona/**/*.ts",
34
+ ".vona-test-*/**/*.ts",
34
35
  "src/backend/package.json",
35
36
  "src/backend/config",
36
37
  "src/backend/play",
@@ -1,25 +0,0 @@
1
- import { execSync } from 'node:child_process';
2
- import { dirname, resolve } from 'node:path';
3
- import { fileURLToPath } from 'node:url';
4
-
5
- const __dirname = dirname(fileURLToPath(import.meta.url));
6
- const ROOT_DIR = resolve(__dirname, '..');
7
-
8
- function exec(cmd: string): void {
9
- execSync(cmd, { stdio: 'inherit', cwd: ROOT_DIR });
10
- }
11
-
12
- function initTestData(): void {
13
- // eslint-disable-next-line no-console
14
- console.log('[init:test-data] Initializing test data via npm run test...');
15
- try {
16
- exec('npm run test');
17
- } catch {
18
- // eslint-disable-next-line no-console
19
- console.warn(
20
- '[init:test-data] npm run test failed after init completed; Redis may be unavailable, skipping test data initialization',
21
- );
22
- }
23
- }
24
-
25
- initTestData();