cabloy 5.1.155 → 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 +1 -1
- package/.claude/skills/cabloy-contract-loop/SKILL.md +1 -1
- package/.claude/skills/cabloy-spec-generation/SKILL.md +1 -1
- package/.claude/skills/cabloy-workflow/SKILL.md +3 -3
- package/CHANGELOG.md +19 -0
- package/CLAUDE.md +1 -1
- package/README.md +18 -18
- package/package.json +1 -1
- package/repo-docs/.vitepress/public/CNAME +1 -1
- package/repo-docs/ai/edition-consistency-checklist.md +7 -7
- package/repo-docs/backend/quickstart.md +1 -1
- package/repo-docs/blogs/ai-react-nextjs-enterprise-architecture-cabloy/index.md +8 -8
- package/repo-docs/blogs/cabloy-fullstack-resource-addressing/index.md +13 -13
- package/repo-docs/blogs/vue-object-oriented-zova-beginner-mental-model/index.md +6 -6
- package/repo-docs/editions/cabloy-start.md +10 -12
- package/repo-docs/editions/choosing-between-basic-and-start.md +6 -7
- package/repo-docs/editions/detection.md +1 -1
- package/repo-docs/editions/overview.md +26 -14
- package/repo-docs/frontend/foundation.md +1 -1
- package/repo-docs/frontend/introduction.md +1 -1
- package/repo-docs/frontend/scripts.md +2 -2
- package/repo-docs/fullstack/deploy-cloudflare-docker.md +1 -1
- package/repo-docs/fullstack/edition-collaboration-differences.md +2 -2
- package/repo-docs/fullstack/introduction.md +7 -3
- package/repo-docs/fullstack/openapi-to-sdk.md +1 -1
- package/repo-docs/fullstack/quickstart.md +4 -4
- package/repo-docs/fullstack/vona-zova-integration.md +1 -1
- package/repo-docs/index.md +1 -1
- package/repo-docs/reference/glossary.md +8 -5
- package/repo-docs/reference/introduction.md +1 -1
- package/repo-docs/reference/repo-scripts.md +12 -4
- package/vona/packages-cli/cli/package.json +1 -1
- package/vona/packages-cli/cli-set-api/package.json +1 -1
- package/vona/packages-utils/cascade-extend/package.json +1 -1
- package/vona/packages-utils/dotenv/package.json +1 -1
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/packages-vona/vona-shared/package.json +4 -4
- package/vona/pnpm-lock.yaml +8 -3
- package/vona/src/suite/a-home/modules/home-user/src/bean/meta.redlock.ts +2 -1
- package/vona/src/suite/a-home/modules/home-user/src/service/roleAdapter.ts +115 -6
- package/vona/src/suite/a-home/modules/home-user/test/role.test.ts +169 -2
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/.metadata/index.ts +26 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/src/bean/eventListener.roleMembershipChanged.ts +25 -0
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-orm/src/service/databaseDialectBase_.ts +104 -33
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-queue/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-queue/src/lib/beanQueueBase.ts +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-queue/src/service/queue.ts +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-user/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-user/src/.metadata/index.ts +15 -0
- package/vona/src/suite-vendor/a-vona/modules/a-user/src/bean/bean.role.ts +44 -3
- package/vona/src/suite-vendor/a-vona/modules/a-user/src/bean/event.roleMembershipChanged.ts +16 -0
- package/vona/src/suite-vendor/a-vona/modules/a-user/src/types/role.ts +12 -1
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/package.json +2 -2
- package/zova/packages-utils/zova-vite/package.json +2 -2
- package/zova/pnpm-lock.yaml +1 -1
- package/.claude/scheduled_tasks.lock +0 -1
- package/vona/packages-utils/cascade-extend/LICENSE +0 -3
|
@@ -109,6 +109,32 @@ declare module 'vona-module-a-event' {
|
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
/** event: end */
|
|
112
|
+
/** eventListener: begin */
|
|
113
|
+
export * from '../bean/eventListener.roleMembershipChanged.ts';
|
|
114
|
+
|
|
115
|
+
import { type IDecoratorEventListenerOptions } from 'vona-module-a-event';
|
|
116
|
+
declare module 'vona-module-a-event' {
|
|
117
|
+
|
|
118
|
+
export interface IEventListenerRecord {
|
|
119
|
+
'a-rbac:roleMembershipChanged': IDecoratorEventListenerOptions;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
declare module 'vona-module-a-rbac' {
|
|
125
|
+
|
|
126
|
+
export interface EventListenerRoleMembershipChanged {
|
|
127
|
+
/** @internal */
|
|
128
|
+
get scope(): ScopeModuleARbac;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export interface EventListenerRoleMembershipChanged {
|
|
132
|
+
get $beanFullName(): 'a-rbac.eventListener.roleMembershipChanged';
|
|
133
|
+
get $onionName(): 'a-rbac:roleMembershipChanged';
|
|
134
|
+
get $onionOptions(): IDecoratorEventListenerOptions;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
/** eventListener: end */
|
|
112
138
|
/** config: begin */
|
|
113
139
|
export * from '../config/config.ts';
|
|
114
140
|
import type { config } from '../config/config.ts';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { IEventExecute, NextEvent } from 'vona-module-a-event';
|
|
2
|
+
import type {
|
|
3
|
+
TypeEventRoleMembershipChangedData,
|
|
4
|
+
TypeEventRoleMembershipChangedResult,
|
|
5
|
+
} from 'vona-module-a-user';
|
|
6
|
+
|
|
7
|
+
import { BeanBase } from 'vona';
|
|
8
|
+
import { EventListener } from 'vona-module-a-event';
|
|
9
|
+
|
|
10
|
+
type TypeEventData = TypeEventRoleMembershipChangedData;
|
|
11
|
+
type TypeEventResult = TypeEventRoleMembershipChangedResult;
|
|
12
|
+
|
|
13
|
+
@EventListener({ match: 'a-user:roleMembershipChanged' })
|
|
14
|
+
export class EventListenerRoleMembershipChanged
|
|
15
|
+
extends BeanBase
|
|
16
|
+
implements IEventExecute<TypeEventData, TypeEventResult>
|
|
17
|
+
{
|
|
18
|
+
async execute(
|
|
19
|
+
data: TypeEventData,
|
|
20
|
+
next: NextEvent<TypeEventData, TypeEventResult>,
|
|
21
|
+
): Promise<TypeEventResult> {
|
|
22
|
+
await this.scope.event.policyInvalidated.emit({ kind: 'role' });
|
|
23
|
+
return await next(data);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vona-suite-a-cabloy",
|
|
3
|
-
"version": "5.1.
|
|
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.
|
|
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:^"
|
|
@@ -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
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
'
|
|
27
|
-
'
|
|
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(
|
|
194
|
+
protected _coerceColumnValue(column: Pick<ITableColumn, 'type' | 'columnType'>, value: unknown) {
|
|
195
195
|
if (isNil(value)) return undefined;
|
|
196
|
-
if (
|
|
197
|
-
|
|
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
|
-
|
|
205
|
-
|
|
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
|
-
|
|
208
|
-
|
|
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
|
-
|
|
211
|
-
if (value.indexOf('NULL::') === 0) return undefined;
|
|
212
|
-
return value;
|
|
248
|
+
return literal;
|
|
213
249
|
}
|
|
214
250
|
|
|
215
|
-
|
|
216
|
-
|
|
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.
|
|
288
|
+
return Number.isSafeInteger(num) ? num : value;
|
|
219
289
|
}
|
|
220
290
|
|
|
221
|
-
protected
|
|
222
|
-
|
|
291
|
+
protected _safeNumber(value: string) {
|
|
292
|
+
const num = Number(value);
|
|
293
|
+
return Number.isFinite(num) ? num : undefined;
|
|
223
294
|
}
|
|
224
295
|
}
|
|
@@ -11,7 +11,7 @@ export class BeanQueueBase<DATA = unknown, RESULT = unknown> extends BeanBase {
|
|
|
11
11
|
);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
push(data: DATA, options?: IQueuePushOptions) {
|
|
14
|
+
push(data: DATA, options?: IQueuePushOptions): Promise<void> {
|
|
15
15
|
return this.$scope.queue.service.queue.push(
|
|
16
16
|
this.$scope.queue.service.queue.prepareJobInfo(this.$onionName as any, data, options),
|
|
17
17
|
);
|
|
@@ -24,9 +24,9 @@ export class ServiceQueue extends BeanBase {
|
|
|
24
24
|
_queues: IQueueQueues = {};
|
|
25
25
|
_queueCallbacks: IQueueCallbacks = {};
|
|
26
26
|
|
|
27
|
-
push<DATA>(info: IQueueJobContext<DATA>) {
|
|
27
|
+
push<DATA>(info: IQueueJobContext<DATA>): Promise<void> {
|
|
28
28
|
if (!info.options?.dbInfo) throw new Error('should specify the options.dbInfo');
|
|
29
|
-
this._queuePush(info, false);
|
|
29
|
+
return this._queuePush(info, false);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
// { locale, instanceName, module, queueName,queueNameSub,data }
|
|
@@ -174,6 +174,7 @@ export interface IModuleCacheRedis {
|
|
|
174
174
|
export * from '../bean/event.activate.ts';
|
|
175
175
|
export * from '../bean/event.createAnonymous.ts';
|
|
176
176
|
export * from '../bean/event.register.ts';
|
|
177
|
+
export * from '../bean/event.roleMembershipChanged.ts';
|
|
177
178
|
export * from '../bean/event.signin.ts';
|
|
178
179
|
export * from '../bean/event.signout.ts';
|
|
179
180
|
|
|
@@ -214,6 +215,16 @@ declare module 'vona-module-a-user' {
|
|
|
214
215
|
get $onionName(): 'a-user:register';
|
|
215
216
|
}
|
|
216
217
|
|
|
218
|
+
export interface EventRoleMembershipChanged {
|
|
219
|
+
/** @internal */
|
|
220
|
+
get scope(): ScopeModuleAUser;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export interface EventRoleMembershipChanged {
|
|
224
|
+
get $beanFullName(): 'a-user.event.roleMembershipChanged';
|
|
225
|
+
get $onionName(): 'a-user:roleMembershipChanged';
|
|
226
|
+
}
|
|
227
|
+
|
|
217
228
|
export interface EventSignin {
|
|
218
229
|
/** @internal */
|
|
219
230
|
get scope(): ScopeModuleAUser;
|
|
@@ -239,12 +250,14 @@ declare module 'vona-module-a-user' {
|
|
|
239
250
|
import type { EventActivate } from '../bean/event.activate.ts';
|
|
240
251
|
import type { EventCreateAnonymous } from '../bean/event.createAnonymous.ts';
|
|
241
252
|
import type { EventRegister } from '../bean/event.register.ts';
|
|
253
|
+
import type { EventRoleMembershipChanged } from '../bean/event.roleMembershipChanged.ts';
|
|
242
254
|
import type { EventSignin } from '../bean/event.signin.ts';
|
|
243
255
|
import type { EventSignout } from '../bean/event.signout.ts';
|
|
244
256
|
export interface IModuleEvent {
|
|
245
257
|
'activate': EventActivate;
|
|
246
258
|
'createAnonymous': EventCreateAnonymous;
|
|
247
259
|
'register': EventRegister;
|
|
260
|
+
'roleMembershipChanged': EventRoleMembershipChanged;
|
|
248
261
|
'signin': EventSignin;
|
|
249
262
|
'signout': EventSignout;
|
|
250
263
|
}
|
|
@@ -253,6 +266,7 @@ export interface IModuleEvent {
|
|
|
253
266
|
import type { TypeEventActivateData, TypeEventActivateResult } from '../bean/event.activate.ts';
|
|
254
267
|
import type { TypeEventCreateAnonymousData, TypeEventCreateAnonymousResult } from '../bean/event.createAnonymous.ts';
|
|
255
268
|
import type { TypeEventRegisterData, TypeEventRegisterResult } from '../bean/event.register.ts';
|
|
269
|
+
import type { TypeEventRoleMembershipChangedData, TypeEventRoleMembershipChangedResult } from '../bean/event.roleMembershipChanged.ts';
|
|
256
270
|
import type { TypeEventSigninData, TypeEventSigninResult } from '../bean/event.signin.ts';
|
|
257
271
|
import type { TypeEventSignoutData, TypeEventSignoutResult } from '../bean/event.signout.ts';
|
|
258
272
|
import type { EventOn } from 'vona-module-a-event';
|
|
@@ -261,6 +275,7 @@ declare module 'vona-module-a-event' {
|
|
|
261
275
|
'a-user:activate': EventOn<TypeEventActivateData, TypeEventActivateResult>;
|
|
262
276
|
'a-user:createAnonymous': EventOn<TypeEventCreateAnonymousData, TypeEventCreateAnonymousResult>;
|
|
263
277
|
'a-user:register': EventOn<TypeEventRegisterData, TypeEventRegisterResult>;
|
|
278
|
+
'a-user:roleMembershipChanged': EventOn<TypeEventRoleMembershipChangedData, TypeEventRoleMembershipChangedResult>;
|
|
264
279
|
'a-user:signin': EventOn<TypeEventSigninData, TypeEventSigninResult>;
|
|
265
280
|
'a-user:signout': EventOn<TypeEventSignoutData, TypeEventSignoutResult>;
|
|
266
281
|
}
|
|
@@ -2,8 +2,9 @@ import type { TableIdentity } from 'table-identity';
|
|
|
2
2
|
|
|
3
3
|
import { BeanBase, beanFullNameFromOnionName } from 'vona';
|
|
4
4
|
import { Bean } from 'vona-module-a-bean';
|
|
5
|
+
import { Core } from 'vona-module-a-core';
|
|
5
6
|
|
|
6
|
-
import type { IRole, IRoleAdapter } from '../types/role.ts';
|
|
7
|
+
import type { IRole, IRoleAdapter, IRoleMembershipReplaceResult } from '../types/role.ts';
|
|
7
8
|
|
|
8
9
|
@Bean()
|
|
9
10
|
export class BeanRole extends BeanBase {
|
|
@@ -33,7 +34,47 @@ export class BeanRole extends BeanBase {
|
|
|
33
34
|
return this.roleAdapter.findAllByUserId(userId);
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
@Core.transaction()
|
|
38
|
+
async addUserId(id: TableIdentity, userId: TableIdentity): Promise<boolean> {
|
|
39
|
+
const changed = await this.roleAdapter.addUserId(id, userId);
|
|
40
|
+
if (changed) await this.emitMembershipChanged(userId, [id]);
|
|
41
|
+
return changed;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@Core.transaction()
|
|
45
|
+
async removeUserId(id: TableIdentity, userId: TableIdentity): Promise<boolean> {
|
|
46
|
+
const changed = await this.roleAdapter.removeUserId(id, userId);
|
|
47
|
+
if (changed) await this.emitMembershipChanged(userId, [id]);
|
|
48
|
+
return changed;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@Core.transaction()
|
|
52
|
+
async replaceUserRoleIds(
|
|
53
|
+
userId: TableIdentity,
|
|
54
|
+
roleIds: TableIdentity[],
|
|
55
|
+
options?: { preserveRoleIds?: TableIdentity[] },
|
|
56
|
+
): Promise<IRoleMembershipReplaceResult> {
|
|
57
|
+
const result = await this.roleAdapter.replaceUserRoleIds(userId, roleIds, options);
|
|
58
|
+
const changedRoleIds = [...result.addedRoleIds, ...result.removedRoleIds];
|
|
59
|
+
if (changedRoleIds.length) await this.emitMembershipChanged(userId, changedRoleIds);
|
|
60
|
+
return result;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
private async emitMembershipChanged(
|
|
64
|
+
userId: TableIdentity,
|
|
65
|
+
roleIds: TableIdentity[],
|
|
66
|
+
): Promise<void> {
|
|
67
|
+
await this.scope.event.roleMembershipChanged.emit({
|
|
68
|
+
userIds: this.uniqueIds([userId]),
|
|
69
|
+
roleIds: this.uniqueIds(roleIds),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private uniqueIds(ids: TableIdentity[]): TableIdentity[] {
|
|
74
|
+
const values = new Map<string, TableIdentity>();
|
|
75
|
+
for (const id of ids) values.set(String(id), id);
|
|
76
|
+
return [...values.entries()]
|
|
77
|
+
.toSorted(([left], [right]) => left.localeCompare(right))
|
|
78
|
+
.map(([, id]) => id);
|
|
38
79
|
}
|
|
39
80
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TableIdentity } from 'table-identity';
|
|
2
|
+
|
|
3
|
+
import { BeanEventBase, Event } from 'vona-module-a-event';
|
|
4
|
+
|
|
5
|
+
export interface TypeEventRoleMembershipChangedData {
|
|
6
|
+
userIds: TableIdentity[];
|
|
7
|
+
roleIds: TableIdentity[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type TypeEventRoleMembershipChangedResult = void;
|
|
11
|
+
|
|
12
|
+
@Event()
|
|
13
|
+
export class EventRoleMembershipChanged extends BeanEventBase<
|
|
14
|
+
TypeEventRoleMembershipChangedData,
|
|
15
|
+
TypeEventRoleMembershipChangedResult
|
|
16
|
+
> {}
|
|
@@ -18,9 +18,20 @@ export interface IRole {
|
|
|
18
18
|
builtin?: boolean;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
export interface IRoleMembershipReplaceResult {
|
|
22
|
+
addedRoleIds: TableIdentity[];
|
|
23
|
+
removedRoleIds: TableIdentity[];
|
|
24
|
+
}
|
|
25
|
+
|
|
21
26
|
export interface IRoleAdapter {
|
|
22
27
|
findOneByName(name: string): Promise<IRole | undefined>;
|
|
23
28
|
findOne(role: Partial<IRole>): Promise<IRole | undefined>;
|
|
24
29
|
findAllByUserId(userId: TableIdentity): Promise<IRole[] | undefined>;
|
|
25
|
-
addUserId(id: TableIdentity, userId: TableIdentity): Promise<
|
|
30
|
+
addUserId(id: TableIdentity, userId: TableIdentity): Promise<boolean>;
|
|
31
|
+
removeUserId(id: TableIdentity, userId: TableIdentity): Promise<boolean>;
|
|
32
|
+
replaceUserRoleIds(
|
|
33
|
+
userId: TableIdentity,
|
|
34
|
+
roleIds: TableIdentity[],
|
|
35
|
+
options?: { preserveRoleIds?: TableIdentity[] },
|
|
36
|
+
): Promise<IRoleMembershipReplaceResult>;
|
|
26
37
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova-cli",
|
|
3
|
-
"version": "1.2.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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",
|
package/zova/pnpm-lock.yaml
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"sessionId":"834e10b3-c7de-4e46-8255-347f58ac99c9","pid":21283,"procStart":"Tue Sep 1 21:20:05 2026","acquiredAt":1788301241462}
|