cabloy 5.1.156 → 5.1.157

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/.cabloy-version CHANGED
@@ -1 +1 @@
1
- 5.1.156
1
+ 5.1.157
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.1.157
4
+
5
+ ### Features
6
+
7
+ - Add `coerceColumn` support.
8
+
3
9
  ## 5.1.156
4
10
 
5
11
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cabloy",
3
- "version": "5.1.156",
3
+ "version": "5.1.157",
4
4
  "gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
5
5
  "description": "A Node.js fullstack framework",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-cli",
3
- "version": "1.1.142",
3
+ "version": "1.1.143",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona cli",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-cli-set-api",
3
- "version": "1.1.140",
3
+ "version": "1.1.141",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "vona cli-set-api",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cascade-extend",
3
- "version": "2.2.8",
3
+ "version": "2.2.9",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "cascade-extend",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cabloy/dotenv",
3
- "version": "1.2.8",
3
+ "version": "1.2.9",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "cabloy dotenv",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona",
3
- "version": "5.1.90",
3
+ "version": "5.1.91",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "Vona is an intuitive, elegant and powerful Node.js framework for rapidly developing enterprise applications of any size",
6
6
  "keywords": [
@@ -45,11 +45,13 @@ describe('role.test.ts', { concurrency: false }, () => {
45
45
  it('bean:role emits only actual membership changes', async () => {
46
46
  let userId: string | undefined;
47
47
  let roleId: string | undefined;
48
- const events: unknown[] = [];
48
+ const events: Array<{ userIds: unknown[]; roleIds: unknown[] }> = [];
49
49
  const event = app.scope('a-user').event.roleMembershipChanged;
50
50
  const originalEmit = event.emit.bind(event);
51
51
  const emitMock = mock.method(event, 'emit', async data => {
52
- events.push(data);
52
+ if (data.userIds.some(item => String(item) === userId)) {
53
+ events.push(data);
54
+ }
53
55
  return await originalEmit(data);
54
56
  });
55
57
  try {
@@ -143,11 +145,13 @@ describe('role.test.ts', { concurrency: false }, () => {
143
145
  it('bean:role replaces a membership set with one change event', async () => {
144
146
  let userId: string | undefined;
145
147
  const roleIds: string[] = [];
146
- const events: unknown[] = [];
148
+ const events: Array<{ userIds: unknown[]; roleIds: unknown[] }> = [];
147
149
  const event = app.scope('a-user').event.roleMembershipChanged;
148
150
  const originalEmit = event.emit.bind(event);
149
151
  const emitMock = mock.method(event, 'emit', async data => {
150
- events.push(data);
152
+ if (data.userIds.some(item => String(item) === userId)) {
153
+ events.push(data);
154
+ }
151
155
  return await originalEmit(data);
152
156
  });
153
157
  try {
@@ -175,7 +179,9 @@ describe('role.test.ts', { concurrency: false }, () => {
175
179
 
176
180
  const result = await app.bean.role.replaceUserRoleIds(user.id, [roles[1].id, roles[2].id]);
177
181
  assert.deepEqual(result, { addedRoleIds: [roles[2].id], removedRoleIds: [roles[0].id] });
178
- assert.deepEqual(events, [{ userIds: [user.id], roleIds: [roles[0].id, roles[2].id] }]);
182
+ assert.equal(events.length, 1);
183
+ assert.deepEqual(events[0]?.userIds, [user.id]);
184
+ assert.deepEqual(events[0]?.roleIds.toSorted(), [roles[0].id, roles[2].id].toSorted());
179
185
 
180
186
  const memberships = await homeUser.model.roleUser.select({ where: { userId: user.id } });
181
187
  assert.deepEqual(
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-rbac",
3
- "version": "5.0.3",
3
+ "version": "5.0.4",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "Vona Module"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-suite-a-cabloy",
3
- "version": "5.1.49",
3
+ "version": "5.1.50",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "author": "",
@@ -9,7 +9,7 @@
9
9
  "dependencies": {
10
10
  "vona-module-a-cabloy": "workspace:^",
11
11
  "vona-module-a-markdown": "^5.0.3",
12
- "vona-module-a-rbac": "^5.0.3",
12
+ "vona-module-a-rbac": "^5.0.4",
13
13
  "vona-module-a-socket": "workspace:^",
14
14
  "vona-module-a-ssr": "workspace:^",
15
15
  "vona-module-a-ssrhmr": "workspace:^"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-orm",
3
- "version": "5.1.30",
3
+ "version": "5.1.31",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "db",
6
6
  "keywords": [
@@ -15,17 +15,17 @@ import type {
15
15
  } from '../types/dialect.ts';
16
16
 
17
17
  const BOOLEAN_COLUMN_TYPES = ['bit', 'bool', 'boolean'];
18
- const NUMBER_COLUMN_TYPES = ['int'];
19
- const TIMESTAMP_COLUMN_TYPE_PREFIXES = ['timestamp'];
20
- const FLOAT_COLUMN_TYPE_PREFIXES = ['float', 'double'];
21
- const INTEGER_COLUMN_TYPE_PREFIXES = [
22
- 'tinyint',
23
- 'smallint',
24
- 'mediumint',
25
- 'bigint',
26
- 'numeric',
27
- 'integer',
28
- ];
18
+ const INTEGER_COLUMN_TYPES = ['tinyint', 'smallint', 'mediumint', 'int', 'integer'];
19
+ const FLOAT_COLUMN_TYPES = ['float', 'double'];
20
+ const GENERATED_DEFAULT_PATTERN =
21
+ /^(?:current_timestamp\b(?:\s*\(\s*\d*\s*\))?|current_date\b|current_time\b(?:\s*\(\s*\d*\s*\))?|now\s*\(\s*\)|nextval\s*\()/i;
22
+ const NUMBER_LITERAL_PATTERN = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
23
+ const QUOTED_LITERAL_PATTERN = /^'((?:''|[^'])*)'$/;
24
+
25
+ type ColumnDefaultLiteral =
26
+ | { kind: 'string'; value: string }
27
+ | { kind: 'boolean'; value: boolean }
28
+ | { kind: 'number'; value: string };
29
29
 
30
30
  @Service()
31
31
  @Virtual()
@@ -96,13 +96,13 @@ export class ServiceDatabaseDialectBase extends BeanBase {
96
96
 
97
97
  coerceColumn(column: Knex.ColumnInfo): ITableColumn {
98
98
  const result = { type: column.type } as ITableColumn;
99
- result.default = this._coerceColumnValue(column.type, column.defaultValue);
100
99
  const columnType = (column as Knex.ColumnInfo & { columnType?: string }).columnType;
101
100
  if (columnType) result.columnType = columnType;
101
+ result.default = this._coerceColumnValue(result, column.defaultValue);
102
102
  return result;
103
103
  }
104
104
 
105
- protected _isMysqlBooleanColumn(column: ITableColumn) {
105
+ protected _isMysqlBooleanColumn(column: Pick<ITableColumn, 'columnType'>) {
106
106
  return column.columnType?.trim().toLowerCase() === 'tinyint(1)';
107
107
  }
108
108
 
@@ -191,34 +191,105 @@ export class ServiceDatabaseDialectBase extends BeanBase {
191
191
  return [items.map(item => item.id), builder];
192
192
  }
193
193
 
194
- protected _coerceColumnValue(type: string, value) {
194
+ protected _coerceColumnValue(column: Pick<ITableColumn, 'type' | 'columnType'>, value: unknown) {
195
195
  if (isNil(value)) return undefined;
196
- if (BOOLEAN_COLUMN_TYPES.includes(type)) return safeBoolean(value);
197
- if (NUMBER_COLUMN_TYPES.includes(type)) return this._safeNumber(value);
198
- if (
199
- this._columnTypePrefixes(type, TIMESTAMP_COLUMN_TYPE_PREFIXES) &&
200
- value === 'CURRENT_TIMESTAMP'
201
- ) {
202
- return undefined; // new Date();
196
+ if (typeof value !== 'string') {
197
+ return this._isBooleanColumn(column) ? safeBoolean(value as any) : value;
203
198
  }
204
- if (this._columnTypePrefixes(type, FLOAT_COLUMN_TYPE_PREFIXES)) {
205
- return this._safeNumber(value);
199
+
200
+ const literal = this._coerceColumnLiteral(value);
201
+ if (literal === undefined) return undefined;
202
+ if (this._isBooleanColumn(column)) return safeBoolean(literal.value);
203
+ if (literal.kind === 'boolean') return literal.value;
204
+
205
+ const type = this._columnTypeName(column.type);
206
+ if (literal.kind === 'string') return literal.value;
207
+ if (INTEGER_COLUMN_TYPES.includes(type)) return this._safeInteger(literal.value);
208
+ if (FLOAT_COLUMN_TYPES.includes(type)) return this._safeNumber(literal.value);
209
+ return literal.value;
210
+ }
211
+
212
+ protected _isBooleanColumn(column: Pick<ITableColumn, 'type' | 'columnType'>) {
213
+ return (
214
+ this._isMysqlBooleanColumn(column) ||
215
+ BOOLEAN_COLUMN_TYPES.includes(this._columnTypeName(column.type))
216
+ );
217
+ }
218
+
219
+ protected _columnTypeName(type: string) {
220
+ return type.trim().toLowerCase().match(/^\w+/)?.[0] ?? '';
221
+ }
222
+
223
+ protected _coerceColumnLiteral(value: string): ColumnDefaultLiteral | undefined {
224
+ const literal = this._unwrapColumnDefault(value);
225
+ if (literal === '') return undefined;
226
+ const literalLower = literal.toLowerCase();
227
+ if (literalLower === 'null' || GENERATED_DEFAULT_PATTERN.test(literal)) return undefined;
228
+ const quoted = literal.match(QUOTED_LITERAL_PATTERN);
229
+ if (quoted) return { kind: 'string', value: quoted[1].replaceAll("''", "'") };
230
+ if (literalLower === 'true' || literalLower === 't') return { kind: 'boolean', value: true };
231
+ if (literalLower === 'false' || literalLower === 'f') return { kind: 'boolean', value: false };
232
+ if (NUMBER_LITERAL_PATTERN.test(literal)) return { kind: 'number', value: literal };
233
+ if (this._isExpressionColumnDefault(literal)) return undefined;
234
+ return { kind: 'string', value: literal };
235
+ }
236
+
237
+ protected _unwrapColumnDefault(value: string) {
238
+ let literal = value.trim();
239
+ while (literal.startsWith('(') && literal.endsWith(')')) {
240
+ const inner = literal.slice(1, -1).trim();
241
+ if (!inner || !this._isColumnDefaultBalanced(inner)) break;
242
+ literal = inner;
206
243
  }
207
- if (this._columnTypePrefixes(type, INTEGER_COLUMN_TYPE_PREFIXES)) {
208
- return this._safeNumber(value);
244
+ const castIndex = literal.indexOf('::');
245
+ if (castIndex > 0 && this._isScalarColumnDefault(literal.slice(0, castIndex).trim())) {
246
+ literal = literal.slice(0, castIndex).trim();
209
247
  }
210
- // pg: NULL::character varying
211
- if (value.indexOf('NULL::') === 0) return undefined;
212
- return value;
248
+ return literal;
213
249
  }
214
250
 
215
- // pg: nextval
216
- protected _safeNumber(value) {
251
+ protected _isScalarColumnDefault(value: string) {
252
+ return (
253
+ value.toLowerCase() === 'null' ||
254
+ QUOTED_LITERAL_PATTERN.test(value) ||
255
+ value.toLowerCase() === 'true' ||
256
+ value.toLowerCase() === 'false' ||
257
+ NUMBER_LITERAL_PATTERN.test(value)
258
+ );
259
+ }
260
+
261
+ protected _isColumnDefaultBalanced(value: string) {
262
+ let depth = 0;
263
+ let quoted = false;
264
+ for (let index = 0; index < value.length; index++) {
265
+ const char = value[index];
266
+ if (char === "'") {
267
+ if (quoted && value[index + 1] === "'") {
268
+ index++;
269
+ } else {
270
+ quoted = !quoted;
271
+ }
272
+ } else if (!quoted && char === '(') {
273
+ depth++;
274
+ } else if (!quoted && char === ')') {
275
+ if (depth === 0) return false;
276
+ depth--;
277
+ }
278
+ }
279
+ return !quoted && depth === 0;
280
+ }
281
+
282
+ protected _isExpressionColumnDefault(value: string) {
283
+ return /[()*/+\-\s]/.test(value);
284
+ }
285
+
286
+ protected _safeInteger(value: string) {
217
287
  const num = Number(value);
218
- return Number.isNaN(num) ? undefined : num;
288
+ return Number.isSafeInteger(num) ? num : value;
219
289
  }
220
290
 
221
- protected _columnTypePrefixes(type: string, prefixes: string[]) {
222
- return prefixes.some(prefix => type.includes(prefix));
291
+ protected _safeNumber(value: string) {
292
+ const num = Number(value);
293
+ return Number.isFinite(num) ? num : undefined;
223
294
  }
224
295
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-module-a-permission",
3
- "version": "5.1.19",
3
+ "version": "5.1.20",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vona-suite-a-vona",
3
- "version": "5.1.83",
3
+ "version": "5.1.84",
4
4
  "gitHead": "a79189b882c17af5911573896a781bbb0046d37d",
5
5
  "description": "",
6
6
  "author": "",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-cli",
3
- "version": "1.2.104",
3
+ "version": "1.2.105",
4
4
  "gitHead": "6f675a8cc46d596142c591c28a40cc4d82fcc6cc",
5
5
  "description": "zova cli",
6
6
  "keywords": [
@@ -44,7 +44,7 @@
44
44
  "@cabloy/process-helper": "^3.1.8",
45
45
  "fs-extra": "^11.3.5",
46
46
  "semver": "^7.6.2",
47
- "zova-cli-set-front": "^1.2.102"
47
+ "zova-cli-set-front": "^1.2.103"
48
48
  },
49
49
  "devDependencies": {
50
50
  "clean-package": "^2.2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-cli-set-front",
3
- "version": "1.2.102",
3
+ "version": "1.2.103",
4
4
  "gitHead": "6f675a8cc46d596142c591c28a40cc4d82fcc6cc",
5
5
  "description": "zova cli-set-front",
6
6
  "keywords": [
@@ -70,7 +70,7 @@
70
70
  "vite": "^8.0.14",
71
71
  "yaml": "^2.8.3",
72
72
  "zova-openapi": "^1.1.17",
73
- "zova-vite": "^1.1.44"
73
+ "zova-vite": "^1.1.45"
74
74
  },
75
75
  "devDependencies": {
76
76
  "clean-package": "^2.2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-vite",
3
- "version": "1.1.44",
3
+ "version": "1.1.45",
4
4
  "gitHead": "09d901d17140a80ee0764211b441cda72fd94663",
5
5
  "description": "zova vite",
6
6
  "keywords": [
@@ -40,7 +40,7 @@
40
40
  "@babel/plugin-proposal-decorators": "^7.29.0",
41
41
  "@babel/plugin-transform-class-properties": "^7.28.6",
42
42
  "@babel/plugin-transform-typescript": "^7.28.6",
43
- "@cabloy/dotenv": "^1.2.8",
43
+ "@cabloy/dotenv": "^1.2.9",
44
44
  "@cabloy/extend": "^3.2.8",
45
45
  "@cabloy/module-glob": "^5.3.18",
46
46
  "@cabloy/module-info": "^2.0.3",
@@ -1 +0,0 @@
1
- {"sessionId":"834e10b3-c7de-4e46-8255-347f58ac99c9","pid":21283,"procStart":"Tue Sep 1 21:20:05 2026","acquiredAt":1788301241462}
@@ -1,3 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2016-present CabloyJS