cabloy 5.1.152 → 5.1.154
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-module-removal/SKILL.md +1 -1
- package/.claude/skills/cabloy-resource-field-update/SKILL.md +1 -1
- package/.claude/skills/cabloy-spec-generation/SKILL.md +1 -1
- package/.claude/skills/cabloy-spec-generation/references/repo-aware-discovery.md +1 -1
- package/.claude/skills/cabloy-spec-generation/scripts/generate-implementation-charts.mjs +46 -11
- package/.claude/skills/cabloy-spec-generation/scripts/generate-implementation-charts.test.mjs +13 -0
- package/.claude/skills/cabloy-workflow/SKILL.md +13 -14
- package/.claude/skills/cabloy-workflow/evals/evals.json +4 -4
- package/CHANGELOG.md +27 -0
- package/CLAUDE.md +2 -3
- package/package.json +1 -1
- package/repo-docs/.vitepress/config.mjs +5 -0
- package/repo-docs/ai/cli-to-skill-map.md +19 -0
- package/repo-docs/ai/docs-skills-rules-mapping.md +21 -6
- package/repo-docs/ai/future-skill-roadmap.md +17 -72
- package/repo-docs/ai/introduction.md +15 -5
- package/repo-docs/ai/playbook-spec-execution.md +144 -0
- package/repo-docs/ai/playbook-spec-generation.md +155 -0
- package/repo-docs/ai/repo-guidance.md +9 -5
- package/repo-docs/ai/rules-and-config.md +2 -2
- package/repo-docs/ai/skills.md +4 -0
- package/repo-docs/ai/virtual-decorator-guidance.md +1 -1
- package/repo-docs/backend/introduction.md +1 -1
- package/repo-docs/backend/menu-guide.md +3 -2
- package/repo-docs/frontend/model-resource-cookbook.md +38 -7
- package/repo-docs/frontend/model-resource-usage-guide.md +2 -1
- package/repo-docs/frontend/router-tabs-introduction.md +1 -1
- package/repo-docs/frontend/use-state-data-best-practices.md +39 -6
- package/repo-docs/reference/package-map.md +4 -1
- package/repo-docs/reference/repo-scripts.md +25 -0
- package/repo-e2e/specs/cabloy-basic.spec.ts +69 -20
- package/test-results/.last-run.json +4 -2
- package/test-results/cabloy-basic-ATP-BASIC-SUM-58d8a-ry-states-and-Markdown-HTML/error-context.md +195 -0
- package/vona/env/.env +8 -0
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/pnpm-lock.yaml +81 -145
- package/vona/src/suite/a-commerce/modules/commerce-siteadmin/package.json +1 -2
- package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/.metadata/index.ts +40 -65
- package/vona/src/suite/a-home/modules/home-base/src/.metadata/index.ts +78 -72
- package/vona/src/suite/a-home/modules/home-base/src/.metadata/locales.ts +18 -0
- package/vona/src/suite/a-home/modules/home-base/src/config/locale/en-us.ts +3 -0
- package/vona/src/suite/a-home/modules/home-base/src/config/locale/zh-cn.ts +3 -0
- package/vona/src/suite/a-home/modules/home-base/src/index.ts +1 -0
- package/vona/src/suite/a-home/modules/home-base/src/service/siteCatalog.ts +17 -10
- package/vona/src/suite/a-home/modules/home-user/src/bean/meta.version.ts +5 -0
- package/vona/src/suite/a-home/modules/home-user/src/config/config.ts +36 -5
- package/vona/src/suite/a-home/modules/home-user/src/config/roles.ts +7 -2
- package/vona/src/suite/a-home/modules/home-user/src/entity/role.ts +3 -0
- package/vona/src/suite/a-home/modules/home-user/src/types/env.ts +11 -0
- package/vona/src/suite/a-home/modules/home-user/src/types/index.ts +1 -0
- package/vona/src/suite/a-home/modules/home-user/test/config.test.ts +65 -0
- package/vona/src/suite/a-home/modules/home-user/test/role.test.ts +14 -6
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/.metadata/index.ts +17 -2
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/bean/event.resolveMenuVisibility.ts +19 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/lib/beanSsrSiteBase.ts +32 -16
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/lib/index.ts +2 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/lib/ssrMenuEligibility.ts +107 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/lib/ssrMenuVisibility.ts +91 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/service/ssr.ts +30 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/src/types/ssrMenu.ts +67 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/test/ssrMenuEligibility.test.ts +135 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-ssr/test/ssrMenuVisibility.test.ts +64 -0
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/const/index.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-openapiutils/src/lib/const/site.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- 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/types/role.ts +1 -0
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/pnpm-lock.yaml +1 -1
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +3 -2
- package/zova/src/suite/a-home/modules/home-api/mock/passport.fake.ts +6 -6
- package/zova/src/suite/a-home/modules/home-base/src/lib/index.ts +1 -0
- package/zova/src/suite/a-home/modules/home-base/src/lib/roleSiteAdmission.ts +15 -0
- package/zova/src/suite/a-home/modules/home-base/src/service/routerGuards.ts +3 -3
- package/zova/src/suite/a-home/modules/home-layoutadmin/src/model/menu.ts +4 -3
- package/zova/src/suite/a-home/modules/home-layoutweb/src/model/menu.ts +4 -3
- package/zova/src/suite/a-home/modules/home-passport/package.json +2 -1
- package/zova/src/suite/a-home/modules/home-passport/src/model/passport.ts +2 -2
- package/zova/src/suite/a-home/modules/home-passport/test/lib/roleSiteAdmission.test.ts +23 -0
- package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionSummary.tsx +29 -6
- package/zova/src/suite/a-training/modules/training-student/src/config/locale/en-us.ts +1 -0
- package/zova/src/suite/a-training/modules/training-student/src/config/locale/zh-cn.ts +1 -0
- package/zova/src/suite/a-training/modules/training-student/src/model/student.ts +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/lib/mutationSuccess.ts +11 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +12 -7
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/test/lib/mutationSuccess.test.ts +95 -0
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/bean/meta.version.ts +0 -32
- package/vona/src/suite/a-commerce/modules/commerce-siteadmin/test/meta.version.test.ts +0 -44
package/zova/pnpm-lock.yaml
CHANGED
|
@@ -159,8 +159,10 @@ export class ControllerPagePayment extends BeanControllerPageBase {
|
|
|
159
159
|
for (const [attempt, delay] of SettlementPollDelaysMilliseconds.entries()) {
|
|
160
160
|
if (delay > 0) await new Promise(resolve => setTimeout(resolve, delay));
|
|
161
161
|
if (!this._isCurrentSettlementPoll(pollVersion)) return;
|
|
162
|
+
let session: NonNullable<ReturnType<ModelPaymentSession['view']>>['data'];
|
|
162
163
|
try {
|
|
163
|
-
await this.queryPaymentSession?.refetch({ bypassPersister: true });
|
|
164
|
+
const result = await this.queryPaymentSession?.refetch({ bypassPersister: true });
|
|
165
|
+
session = result?.data;
|
|
164
166
|
} catch (error) {
|
|
165
167
|
if (isNotFoundError(error)) {
|
|
166
168
|
this.paymentSessionUnavailable = true;
|
|
@@ -170,7 +172,6 @@ export class ControllerPagePayment extends BeanControllerPageBase {
|
|
|
170
172
|
throw error;
|
|
171
173
|
}
|
|
172
174
|
if (!this._isCurrentSettlementPoll(pollVersion)) return;
|
|
173
|
-
const session = this.queryPaymentSession?.data;
|
|
174
175
|
const sessionState = session?.state;
|
|
175
176
|
this.pendingSessionState = sessionState;
|
|
176
177
|
this.pendingNextAction = session?.nextAction;
|
|
@@ -416,14 +416,14 @@ export default defineFakeRoute([
|
|
|
416
416
|
name: 'registeredUser',
|
|
417
417
|
title: 'Registered User',
|
|
418
418
|
titleLocales: { 'zh-cn': '注册用户' },
|
|
419
|
-
siteIds: ['
|
|
419
|
+
siteIds: ['*'],
|
|
420
420
|
},
|
|
421
421
|
{
|
|
422
422
|
id: 2,
|
|
423
423
|
name: 'systemAdmin',
|
|
424
424
|
title: 'System Administrator',
|
|
425
425
|
titleLocales: { 'zh-cn': '系统管理员' },
|
|
426
|
-
siteIds: ['
|
|
426
|
+
siteIds: ['*'],
|
|
427
427
|
},
|
|
428
428
|
],
|
|
429
429
|
},
|
|
@@ -461,15 +461,15 @@ export default defineFakeRoute([
|
|
|
461
461
|
id: 1,
|
|
462
462
|
name: 'registeredUser',
|
|
463
463
|
title: 'Registered User',
|
|
464
|
-
|
|
465
|
-
siteIds: ['
|
|
464
|
+
titleLocales: { 'zh-cn': '注册用户' },
|
|
465
|
+
siteIds: ['*'],
|
|
466
466
|
},
|
|
467
467
|
{
|
|
468
468
|
id: 2,
|
|
469
469
|
name: 'systemAdmin',
|
|
470
470
|
title: 'System Administrator',
|
|
471
|
-
|
|
472
|
-
siteIds: ['
|
|
471
|
+
titleLocales: { 'zh-cn': '系统管理员' },
|
|
472
|
+
siteIds: ['*'],
|
|
473
473
|
},
|
|
474
474
|
],
|
|
475
475
|
},
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const roleSiteIdAll = '*';
|
|
2
|
+
|
|
3
|
+
export interface RoleSiteAdmission {
|
|
4
|
+
siteIds: string[];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function isRoleSiteAdmitted(
|
|
8
|
+
siteId: string | undefined,
|
|
9
|
+
roles: RoleSiteAdmission[] | undefined,
|
|
10
|
+
): boolean {
|
|
11
|
+
if (!siteId) return false;
|
|
12
|
+
return !!roles?.some(
|
|
13
|
+
role => role.siteIds.includes(siteId) || role.siteIds.includes(roleSiteIdAll),
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -4,6 +4,8 @@ import { catchError } from '@cabloy/utils';
|
|
|
4
4
|
import { Service } from 'zova-module-a-bean';
|
|
5
5
|
import { BeanRouterGuardsBase } from 'zova-module-a-router';
|
|
6
6
|
|
|
7
|
+
import { isRoleSiteAdmitted } from '../lib/roleSiteAdmission.js';
|
|
8
|
+
|
|
7
9
|
@Service()
|
|
8
10
|
export class ServiceRouterGuards extends BeanRouterGuardsBase {
|
|
9
11
|
protected onRouterGuards(router: BeanRouter) {
|
|
@@ -31,9 +33,7 @@ export class ServiceRouterGuards extends BeanRouterGuardsBase {
|
|
|
31
33
|
}
|
|
32
34
|
return pagePath;
|
|
33
35
|
}
|
|
34
|
-
const
|
|
35
|
-
const siteAdmitted =
|
|
36
|
-
!!siteId && !!this.$passport.roles?.some(role => role.siteIds.includes(siteId));
|
|
36
|
+
const siteAdmitted = isRoleSiteAdmitted(this.sys.env.SITE_ID, this.$passport.roles);
|
|
37
37
|
if (!siteAdmitted) {
|
|
38
38
|
const pagePath = this.app.$getPagePathAccessDenied();
|
|
39
39
|
if (process.env.SERVER) {
|
|
@@ -28,7 +28,7 @@ export class ModelMenu extends BeanModelBase {
|
|
|
28
28
|
// event
|
|
29
29
|
if (process.env.CLIENT && this.sys.config.ssr.hmr) {
|
|
30
30
|
this._eventSsrHmrReload = this.sys.meta.event.on('a-ssrhmr:reload', async (_data, next) => {
|
|
31
|
-
await this.
|
|
31
|
+
await this.refreshMenus();
|
|
32
32
|
return next();
|
|
33
33
|
});
|
|
34
34
|
}
|
|
@@ -78,8 +78,9 @@ export class ModelMenu extends BeanModelBase {
|
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
async refreshMenus(): Promise<void> {
|
|
82
|
+
const queryMenus = this.retrieveMenus();
|
|
83
|
+
await queryMenus.refetch({ bypassPersister: true });
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
private _getQueryKeyRetrieveMenus(): [string, string | undefined, string | undefined] {
|
|
@@ -28,7 +28,7 @@ export class ModelMenu extends BeanModelBase {
|
|
|
28
28
|
// event
|
|
29
29
|
if (process.env.CLIENT && this.sys.config.ssr.hmr) {
|
|
30
30
|
this._eventSsrHmrReload = this.sys.meta.event.on('a-ssrhmr:reload', async (_data, next) => {
|
|
31
|
-
await this.
|
|
31
|
+
await this.refreshMenus();
|
|
32
32
|
return next();
|
|
33
33
|
});
|
|
34
34
|
}
|
|
@@ -78,8 +78,9 @@ export class ModelMenu extends BeanModelBase {
|
|
|
78
78
|
});
|
|
79
79
|
}
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
async refreshMenus(): Promise<void> {
|
|
82
|
+
const queryMenus = this.retrieveMenus();
|
|
83
|
+
await queryMenus.refetch({ bypassPersister: true });
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
private _getQueryKeyRetrieveMenus(): [string, string | undefined, string | undefined] {
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
TypeOpenapiPermissions,
|
|
18
18
|
} from 'zova-module-a-openapi';
|
|
19
19
|
import { ApiApiHomeUserPassportloginOauthPath, OpenApiBaseURL } from 'zova-module-home-api';
|
|
20
|
+
import { isRoleSiteAdmitted } from 'zova-module-home-base';
|
|
20
21
|
|
|
21
22
|
import { matchPermissionAction } from '../lib/permissionActionMatcher.js';
|
|
22
23
|
|
|
@@ -143,8 +144,7 @@ export class ModelPassport extends BeanModelBase {
|
|
|
143
144
|
}
|
|
144
145
|
|
|
145
146
|
isPassportSiteAdmitted(passport?: ApiApiHomeUserPassportloginResponseBody['passport']) {
|
|
146
|
-
|
|
147
|
-
return !!siteId && !!passport?.roles.some(role => role.siteIds.includes(siteId));
|
|
147
|
+
return isRoleSiteAdmitted(this.sys.env.SITE_ID, passport?.roles);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
150
|
logout() {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
import { isRoleSiteAdmitted } from 'zova-module-home-base';
|
|
4
|
+
|
|
5
|
+
test('admits an exact site match', () => {
|
|
6
|
+
assert.equal(isRoleSiteAdmitted('web', [{ siteIds: ['web'] }]), true);
|
|
7
|
+
assert.equal(isRoleSiteAdmitted('admin', [{ siteIds: ['web'] }]), false);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('admits every non-empty site through the wildcard', () => {
|
|
11
|
+
assert.equal(isRoleSiteAdmitted('web', [{ siteIds: ['*'] }]), true);
|
|
12
|
+
assert.equal(isRoleSiteAdmitted('admin', [{ siteIds: ['*'] }]), true);
|
|
13
|
+
assert.equal(isRoleSiteAdmitted(undefined, [{ siteIds: ['*'] }]), false);
|
|
14
|
+
assert.equal(isRoleSiteAdmitted('', [{ siteIds: ['*'] }]), false);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test('combines site admission across roles', () => {
|
|
18
|
+
assert.equal(isRoleSiteAdmitted('admin', [{ siteIds: ['web'] }, { siteIds: ['admin'] }]), true);
|
|
19
|
+
assert.equal(
|
|
20
|
+
isRoleSiteAdmitted('web', [{ siteIds: ['storefront'] }, { siteIds: ['admin'] }]),
|
|
21
|
+
false,
|
|
22
|
+
);
|
|
23
|
+
});
|
package/zova/src/suite/a-training/modules/training-student/src/bean/tableCell.actionSummary.tsx
CHANGED
|
@@ -40,15 +40,38 @@ export class TableCellActionSummary extends BeanBase implements ITableCellRender
|
|
|
40
40
|
true,
|
|
41
41
|
)) as ModelStudent;
|
|
42
42
|
const querySummary = modelStudent.summary(id);
|
|
43
|
-
await querySummary.
|
|
43
|
+
await querySummary.suspense();
|
|
44
44
|
$host.$appModal.dialog(
|
|
45
45
|
{
|
|
46
46
|
title: this.scope.locale.Summary(),
|
|
47
|
-
slotDefault: () =>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
slotDefault: () => {
|
|
48
|
+
const hasData = querySummary.data !== undefined;
|
|
49
|
+
const isLoading = !hasData && (querySummary.isPending || querySummary.isFetching);
|
|
50
|
+
const error = querySummary.error;
|
|
51
|
+
return (
|
|
52
|
+
<>
|
|
53
|
+
{hasData && error && (
|
|
54
|
+
<div class="alert alert-warning mb-3" role="alert">
|
|
55
|
+
<span>{this.scope.locale.SummaryRefreshFailed()}</span>
|
|
56
|
+
</div>
|
|
57
|
+
)}
|
|
58
|
+
{error && (
|
|
59
|
+
<div class="alert alert-error mb-3" role="alert">
|
|
60
|
+
<span>{error.message}</span>
|
|
61
|
+
</div>
|
|
62
|
+
)}
|
|
63
|
+
{hasData ? (
|
|
64
|
+
<div class="student-summary-description">
|
|
65
|
+
<ZMarkdownHtml html={querySummary.data?.descriptionHtml ?? ''} />
|
|
66
|
+
</div>
|
|
67
|
+
) : isLoading ? (
|
|
68
|
+
<div class="flex justify-center p-4" role="status">
|
|
69
|
+
<span class="loading loading-spinner text-primary" />
|
|
70
|
+
</div>
|
|
71
|
+
) : undefined}
|
|
72
|
+
</>
|
|
73
|
+
);
|
|
74
|
+
},
|
|
52
75
|
},
|
|
53
76
|
{
|
|
54
77
|
maxWidth: 720,
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface IMutationSuccessOptions {
|
|
2
|
+
invalidateSelect?: () => Promise<void>;
|
|
3
|
+
invalidateItem?: () => Promise<void>;
|
|
4
|
+
onSuccess?: () => void | Promise<void>;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function runMutationSuccess(options: IMutationSuccessOptions) {
|
|
8
|
+
await options.invalidateSelect?.();
|
|
9
|
+
await options.invalidateItem?.();
|
|
10
|
+
await options.onSuccess?.();
|
|
11
|
+
}
|
|
@@ -17,6 +17,7 @@ import { $QueryEnsureLoaded, BeanModelBase, Model } from 'zova-module-a-model';
|
|
|
17
17
|
import { ScopeModuleAOpenapi, SymbolOpenapiSchemaName } from 'zova-module-a-openapi';
|
|
18
18
|
|
|
19
19
|
import { prepareDeleteRequestConfig } from '../lib/deleteRequest.js';
|
|
20
|
+
import { runMutationSuccess } from '../lib/mutationSuccess.js';
|
|
20
21
|
|
|
21
22
|
export interface IModelOptionsResource extends IDecoratorModelOptions {}
|
|
22
23
|
|
|
@@ -154,8 +155,10 @@ export class ModelResource<
|
|
|
154
155
|
this.sys.util.apiActionConfigPrepare(),
|
|
155
156
|
);
|
|
156
157
|
},
|
|
157
|
-
onSuccess: () => {
|
|
158
|
-
|
|
158
|
+
onSuccess: async () => {
|
|
159
|
+
await runMutationSuccess({
|
|
160
|
+
invalidateSelect: () => this.$invalidateQueries({ queryKey: ['select'] }),
|
|
161
|
+
});
|
|
159
162
|
},
|
|
160
163
|
});
|
|
161
164
|
}
|
|
@@ -169,11 +172,13 @@ export class ModelResource<
|
|
|
169
172
|
mutationKey: [...this.keyItem(id, action), 'mutation'],
|
|
170
173
|
mutationFn,
|
|
171
174
|
onSuccess: async (data, variables, context) => {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
175
|
+
await runMutationSuccess({
|
|
176
|
+
invalidateSelect: invalidateSelect
|
|
177
|
+
? () => this.$invalidateQueries({ queryKey: ['select'] })
|
|
178
|
+
: undefined,
|
|
179
|
+
invalidateItem: () => this.$invalidateQueries({ queryKey: this.keyItemRoot(id) }),
|
|
180
|
+
onSuccess: () => onSuccess?.(data, variables, context),
|
|
181
|
+
});
|
|
177
182
|
},
|
|
178
183
|
});
|
|
179
184
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { runMutationSuccess } from '../../src/lib/mutationSuccess.ts';
|
|
5
|
+
|
|
6
|
+
function createDeferred() {
|
|
7
|
+
let resolve: (() => void) | undefined;
|
|
8
|
+
const promise = new Promise<void>(resolve_ => {
|
|
9
|
+
resolve = resolve_;
|
|
10
|
+
});
|
|
11
|
+
return { promise, resolve: () => resolve?.() };
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function flushAsyncWork() {
|
|
15
|
+
await new Promise<void>(resolve => setImmediate(resolve));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
test('waits for select invalidation when used by a create mutation', async () => {
|
|
19
|
+
const select = createDeferred();
|
|
20
|
+
let completed = false;
|
|
21
|
+
const result = runMutationSuccess({
|
|
22
|
+
invalidateSelect: () => select.promise,
|
|
23
|
+
}).then(() => {
|
|
24
|
+
completed = true;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
await flushAsyncWork();
|
|
28
|
+
assert.equal(completed, false);
|
|
29
|
+
|
|
30
|
+
select.resolve();
|
|
31
|
+
await result;
|
|
32
|
+
assert.equal(completed, true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('waits for select and item invalidation before custom success handling', async () => {
|
|
36
|
+
const select = createDeferred();
|
|
37
|
+
const item = createDeferred();
|
|
38
|
+
const calls: string[] = [];
|
|
39
|
+
let completed = false;
|
|
40
|
+
const result = runMutationSuccess({
|
|
41
|
+
invalidateSelect: async () => {
|
|
42
|
+
calls.push('select');
|
|
43
|
+
await select.promise;
|
|
44
|
+
},
|
|
45
|
+
invalidateItem: async () => {
|
|
46
|
+
calls.push('item');
|
|
47
|
+
await item.promise;
|
|
48
|
+
},
|
|
49
|
+
onSuccess: () => {
|
|
50
|
+
calls.push('success');
|
|
51
|
+
},
|
|
52
|
+
}).then(() => {
|
|
53
|
+
completed = true;
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
await flushAsyncWork();
|
|
57
|
+
assert.deepEqual(calls, ['select']);
|
|
58
|
+
assert.equal(completed, false);
|
|
59
|
+
|
|
60
|
+
select.resolve();
|
|
61
|
+
await flushAsyncWork();
|
|
62
|
+
assert.deepEqual(calls, ['select', 'item']);
|
|
63
|
+
assert.equal(completed, false);
|
|
64
|
+
|
|
65
|
+
item.resolve();
|
|
66
|
+
await result;
|
|
67
|
+
assert.deepEqual(calls, ['select', 'item', 'success']);
|
|
68
|
+
assert.equal(completed, true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
test('skips select invalidation while still waiting for item invalidation', async () => {
|
|
72
|
+
const item = createDeferred();
|
|
73
|
+
const calls: string[] = [];
|
|
74
|
+
let completed = false;
|
|
75
|
+
const result = runMutationSuccess({
|
|
76
|
+
invalidateItem: async () => {
|
|
77
|
+
calls.push('item');
|
|
78
|
+
await item.promise;
|
|
79
|
+
},
|
|
80
|
+
onSuccess: () => {
|
|
81
|
+
calls.push('success');
|
|
82
|
+
},
|
|
83
|
+
}).then(() => {
|
|
84
|
+
completed = true;
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
await flushAsyncWork();
|
|
88
|
+
assert.deepEqual(calls, ['item']);
|
|
89
|
+
assert.equal(completed, false);
|
|
90
|
+
|
|
91
|
+
item.resolve();
|
|
92
|
+
await result;
|
|
93
|
+
assert.deepEqual(calls, ['item', 'success']);
|
|
94
|
+
assert.equal(completed, true);
|
|
95
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zova-suite-a-cabloy",
|
|
3
|
-
"version": "5.1.
|
|
3
|
+
"version": "5.1.49",
|
|
4
4
|
"gitHead": "4dece229b6d3f4735930e833afcb4b95ec51bf86",
|
|
5
5
|
"description": "",
|
|
6
6
|
"author": "",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"type": "module",
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"zova-module-a-rbac": "^5.0.1",
|
|
11
|
-
"zova-module-rest-resource": "^5.1.
|
|
11
|
+
"zova-module-rest-resource": "^5.1.48"
|
|
12
12
|
},
|
|
13
13
|
"title": "a-cabloy"
|
|
14
14
|
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
IMetaVersionInit,
|
|
3
|
-
IMetaVersionInitOptions,
|
|
4
|
-
IMetaVersionUpdate,
|
|
5
|
-
IMetaVersionUpdateOptions,
|
|
6
|
-
} from 'vona-module-a-version';
|
|
7
|
-
|
|
8
|
-
import { BeanBase } from 'vona';
|
|
9
|
-
import { Meta } from 'vona-module-a-meta';
|
|
10
|
-
|
|
11
|
-
const roleSiteIds = {
|
|
12
|
-
registeredUser: ['commerce'],
|
|
13
|
-
systemAdmin: ['commerce', 'commerceAdmin'],
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
@Meta()
|
|
17
|
-
export class MetaVersion extends BeanBase implements IMetaVersionUpdate, IMetaVersionInit {
|
|
18
|
-
async update(_options: IMetaVersionUpdateOptions) {}
|
|
19
|
-
|
|
20
|
-
async init(options: IMetaVersionInitOptions) {
|
|
21
|
-
if (options.version !== 1) return;
|
|
22
|
-
const modelRole = this.app.scope('home-user').model.role;
|
|
23
|
-
for (const [roleName, requiredSiteIds] of Object.entries(roleSiteIds)) {
|
|
24
|
-
const role = await modelRole.getByName(roleName);
|
|
25
|
-
if (!role) continue;
|
|
26
|
-
|
|
27
|
-
const siteIds = [...new Set([...role.siteIds, ...requiredSiteIds])];
|
|
28
|
-
if (siteIds.length === role.siteIds.length) continue;
|
|
29
|
-
await modelRole.updateById(role.id, { siteIds });
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import assert from 'node:assert';
|
|
2
|
-
import { describe, it } from 'node:test';
|
|
3
|
-
import { app } from 'vona-mock';
|
|
4
|
-
|
|
5
|
-
describe('meta.version.test.ts', () => {
|
|
6
|
-
it('init:version:1 preserves existing grants and appends Commerce site access once', async () => {
|
|
7
|
-
await app.bean.executor.mockCtx(async () => {
|
|
8
|
-
const modelRole = app.scope('home-user').model.role;
|
|
9
|
-
const roleRegisteredUser = await modelRole.getByName('registeredUser');
|
|
10
|
-
const roleSystemAdmin = await modelRole.getByName('systemAdmin');
|
|
11
|
-
assert.ok(roleRegisteredUser);
|
|
12
|
-
assert.ok(roleSystemAdmin);
|
|
13
|
-
const registeredUserSiteIdsOriginal = roleRegisteredUser.siteIds;
|
|
14
|
-
const systemAdminSiteIdsOriginal = roleSystemAdmin.siteIds;
|
|
15
|
-
try {
|
|
16
|
-
await modelRole.updateById(roleRegisteredUser.id, { siteIds: ['web', 'customSite'] });
|
|
17
|
-
await modelRole.updateById(roleSystemAdmin.id, {
|
|
18
|
-
siteIds: ['web', 'admin', 'customSite', 'commerce'],
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
const metaVersion = app.scope('commerce-siteadmin').meta.version;
|
|
22
|
-
await metaVersion.init({ version: 1 });
|
|
23
|
-
await metaVersion.init({ version: 1 });
|
|
24
|
-
|
|
25
|
-
const registeredUser = await modelRole.getByName('registeredUser');
|
|
26
|
-
assert.deepEqual(registeredUser?.siteIds, ['web', 'customSite', 'commerce']);
|
|
27
|
-
|
|
28
|
-
const systemAdmin = await modelRole.getByName('systemAdmin');
|
|
29
|
-
assert.deepEqual(systemAdmin?.siteIds, [
|
|
30
|
-
'web',
|
|
31
|
-
'admin',
|
|
32
|
-
'customSite',
|
|
33
|
-
'commerce',
|
|
34
|
-
'commerceAdmin',
|
|
35
|
-
]);
|
|
36
|
-
} finally {
|
|
37
|
-
await modelRole.updateById(roleRegisteredUser.id, {
|
|
38
|
-
siteIds: registeredUserSiteIdsOriginal,
|
|
39
|
-
});
|
|
40
|
-
await modelRole.updateById(roleSystemAdmin.id, { siteIds: systemAdminSiteIdsOriginal });
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
});
|