cabloy 5.1.172 → 5.1.174

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 (97) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +12 -0
  3. package/package.json +2 -2
  4. package/repo-docs/frontend/component-guide.md +33 -0
  5. package/repo-docs/frontend/model-resource-cookbook.md +0 -8
  6. package/repo-docs/frontend/model-resource-usage-guide.md +0 -4
  7. package/repo-e2e/specs/cabloy-basic.spec.ts +158 -0
  8. package/repo-e2e/specs/controller-boundary.spec.ts +261 -0
  9. package/test-results/.last-run.json +1 -1
  10. package/vona/packages-utils/lint/package.json +1 -1
  11. package/vona/packages-utils/lint/src/oxc/lint.ts +1 -1
  12. package/vona/packages-utils/lint/src/oxc/lintVue.ts +1 -1
  13. package/vona/pnpm-lock.yaml +238 -46
  14. package/vona/pnpm-workspace.yaml +1 -1
  15. package/zova/package.original.json +1 -1
  16. package/zova/packages-cli/cli/package.json +2 -2
  17. package/zova/packages-cli/cli-set-front/package.json +3 -3
  18. package/zova/packages-utils/babel-plugin-zova-behavior/package.json +2 -1
  19. package/zova/packages-utils/babel-plugin-zova-behavior/src/index.ts +21 -8
  20. package/zova/packages-utils/babel-plugin-zova-behavior/test/behaviorSlots.test.ts +111 -0
  21. package/zova/packages-utils/zova-jsx/package.json +2 -2
  22. package/zova/packages-utils/zova-vite/package.json +2 -2
  23. package/zova/packages-zova/zova/package.json +3 -3
  24. package/zova/packages-zova/zova-core/package.json +1 -1
  25. package/zova/packages-zova/zova-core/src/bean/beanBase.ts +1 -1
  26. package/zova/packages-zova/zova-core/src/bean/beanContainer.ts +95 -39
  27. package/zova/packages-zova/zova-core/src/bean/resource/error/type.ts +9 -0
  28. package/zova/packages-zova/zova-core/src/bean/type.ts +18 -0
  29. package/zova/packages-zova/zova-core/src/components/component.ts +2 -1
  30. package/zova/packages-zova/zova-core/src/composables/useController.ts +155 -25
  31. package/zova/packages-zova/zova-core/src/core/component/error.ts +62 -5
  32. package/zova/packages-zova/zova-core/src/core/context/component.ts +37 -2
  33. package/zova/packages-zova/zova-core/src/core/context/hooks.ts +13 -2
  34. package/zova/packages-zova/zova-core/src/core/context/meta.ts +1 -0
  35. package/zova/packages-zova/zova-core/src/core/context/state.ts +161 -0
  36. package/zova/packages-zova/zova-core/src/core/sys/config.ts +32 -7
  37. package/zova/packages-zova/zova-core/src/types/interface/monkey.ts +15 -0
  38. package/zova/packages-zova/zova-core/src/types/utils/powerPartial.ts +1 -1
  39. package/zova/pnpm-lock.yaml +40 -25
  40. package/zova/src/front/config/config/config.ts +11 -0
  41. package/zova/src/front/config/config/render.tsx +79 -0
  42. package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +0 -1
  43. package/zova/src/suite/a-demo/modules/demo-basic/src/.metadata/component/controllerBoundaryProbe.ts +31 -0
  44. package/zova/src/suite/a-demo/modules/demo-basic/src/.metadata/component/controllerBoundaryProbeInline.ts +34 -0
  45. package/zova/src/suite/a-demo/modules/demo-basic/src/.metadata/component/controllerBoundaryProbeOverride.ts +34 -0
  46. package/zova/src/suite/a-demo/modules/demo-basic/src/.metadata/index.ts +58 -1
  47. package/zova/src/suite/a-demo/modules/demo-basic/src/.metadata/page/controllerBoundary.ts +23 -0
  48. package/zova/src/suite/a-demo/modules/demo-basic/src/component/controllerBoundaryProbe/controller.tsx +36 -0
  49. package/zova/src/suite/a-demo/modules/demo-basic/src/component/controllerBoundaryProbeInline/controller.tsx +34 -0
  50. package/zova/src/suite/a-demo/modules/demo-basic/src/component/controllerBoundaryProbeOverride/controller.tsx +46 -0
  51. package/zova/src/suite/a-demo/modules/demo-basic/src/page/controllerBoundary/controller.tsx +185 -0
  52. package/zova/src/suite/a-demo/modules/demo-basic/src/routes.ts +6 -0
  53. package/zova/src/suite/a-home/modules/home-base/src/config/locale/en-us.ts +1 -0
  54. package/zova/src/suite/a-home/modules/home-base/src/config/locale/zh-cn.ts +1 -0
  55. package/zova/src/suite/a-training/modules/training-student/src/model/student.ts +0 -3
  56. package/zova/src/suite/cabloy-basic/modules/basic-button/package.json +58 -0
  57. package/zova/src/suite/cabloy-basic/modules/basic-button/src/.metadata/component/button.ts +31 -0
  58. package/zova/src/suite/cabloy-basic/modules/basic-button/src/.metadata/index.ts +122 -0
  59. package/zova/src/suite/cabloy-basic/modules/basic-button/src/.metadata/this.ts +2 -0
  60. package/zova/src/suite/cabloy-basic/modules/basic-button/src/bean/behavior.perform.tsx +103 -0
  61. package/zova/src/suite/cabloy-basic/modules/basic-button/src/component/button/controller.tsx +39 -0
  62. package/zova/src/suite/cabloy-basic/modules/basic-button/src/index.ts +1 -0
  63. package/zova/src/suite/cabloy-basic/modules/basic-button/tsconfig.build.json +13 -0
  64. package/zova/src/suite/cabloy-basic/modules/basic-button/tsconfig.json +5 -0
  65. package/zova/src/suite/cabloy-basic/modules/basic-details/package.json +2 -1
  66. package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/tableCell.actionDelete.tsx +4 -4
  67. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx +5 -1
  68. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx +5 -1
  69. package/zova/src/suite/cabloy-basic/modules/basic-table/package.json +2 -1
  70. package/zova/src/suite/cabloy-basic/modules/basic-table/src/bean/tableCell.actionDelete.tsx +4 -4
  71. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/actionRefresh/controller.tsx +10 -8
  72. package/zova/src/suite/cabloy-basic/package.json +1 -0
  73. package/zova/src/suite-vendor/a-cabloy/modules/a-layoutprofile/package.json +1 -1
  74. package/zova/src/suite-vendor/a-cabloy/modules/a-layoutprofile/src/model/layoutprofile.ts +0 -3
  75. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  76. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/.metadata/page/resource.ts +6 -1
  77. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +0 -1
  78. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resource/controller.tsx +5 -1
  79. package/zova/src/suite-vendor/a-cabloy/package.json +3 -3
  80. package/zova/src/suite-vendor/a-zova/modules/a-bean/cli/controller/metadata/generateFilePage.ts +3 -1
  81. package/zova/src/suite-vendor/a-zova/modules/a-bean/package.json +1 -1
  82. package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
  83. package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useMutation.ts +1 -11
  84. package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useQuery.ts +1 -17
  85. package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/utils.ts +2 -1
  86. package/zova/src/suite-vendor/a-zova/modules/a-model/src/types/query.ts +2 -7
  87. package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/mutation.types.ts +14 -0
  88. package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/queryEnsureLoaded.test.ts +56 -0
  89. package/zova/src/suite-vendor/a-zova/modules/a-router/package.json +1 -1
  90. package/zova/src/suite-vendor/a-zova/modules/a-router/src/monkey.ts +8 -0
  91. package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
  92. package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssr.ts +85 -1
  93. package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/monkey.ts +25 -1
  94. package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/types/ssr.ts +15 -1
  95. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  96. package/zova/src/suite-vendor/a-zova/package.json +6 -6
  97. /package/vona/patches/{zova-core@5.1.89.patch → zova-core@5.1.90.patch} +0 -0
@@ -0,0 +1,39 @@
1
+ import type { ButtonHTMLAttributes } from 'vue';
2
+ import type { IComponentOptions } from 'zova';
3
+
4
+ import { BeanControllerBase } from 'zova';
5
+ import { Controller } from 'zova-module-a-bean';
6
+
7
+ import type { IBehaviorOptionsPerform, TypeBehaviorOnError } from '../../bean/behavior.perform.jsx';
8
+
9
+ export interface ControllerButtonProps extends Omit<ButtonHTMLAttributes, 'onError'> {
10
+ loading?: boolean;
11
+ onPerform?: (e: MouseEvent) => Promise<void> | void;
12
+ onError?: TypeBehaviorOnError;
13
+ }
14
+
15
+ @Controller()
16
+ export class ControllerButton extends BeanControllerBase {
17
+ static $propsDefault = {};
18
+ static $componentOptions: IComponentOptions = { inheritAttrs: false };
19
+
20
+ protected async __init__() {}
21
+
22
+ protected render() {
23
+ const { loading, onError, onPerform, ...props } = this.$props as ControllerButtonProps;
24
+ const behaviorPerformOptions: IBehaviorOptionsPerform = {
25
+ isLoading: loading,
26
+ onError,
27
+ onPerform,
28
+ };
29
+ return (
30
+ <button
31
+ {...props}
32
+ type={props.type ?? 'button'}
33
+ bs-basic-button-perform={behaviorPerformOptions}
34
+ >
35
+ {this.$slotDefault?.()}
36
+ </button>
37
+ );
38
+ }
39
+ }
@@ -0,0 +1 @@
1
+ export * from './.metadata/index.js';
@@ -0,0 +1,13 @@
1
+ {
2
+ "extends": "../../tsconfig.base",
3
+ "compilerOptions": {
4
+ "rootDir": "src",
5
+ "outDir": "dist",
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "emitDeclarationOnly": true,
9
+ "allowImportingTsExtensions": true,
10
+ "noEmit": false
11
+ },
12
+ "include": ["src", "src/.metadata/**/*"]
13
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "extends": "../../tsconfig.base",
3
+ "compilerOptions": {},
4
+ "include": ["src", "src/.metadata/**/*"]
5
+ }
@@ -51,7 +51,8 @@
51
51
  "title": "basic-details",
52
52
  "zovaModule": {
53
53
  "dependencies": {
54
- "basic-app": "5.0.0"
54
+ "basic-app": "5.0.0",
55
+ "basic-button": "5.0.0"
55
56
  }
56
57
  }
57
58
  }
@@ -8,6 +8,7 @@ import type {
8
8
  import { BeanBase } from 'zova';
9
9
  import { ZIcon } from 'zova-module-a-icon';
10
10
  import { TableCell } from 'zova-module-a-table';
11
+ import { ZButton } from 'zova-module-basic-button';
11
12
 
12
13
  import { ICommandOptionsDelete } from './command.delete.jsx';
13
14
 
@@ -30,10 +31,9 @@ export class TableCellActionDelete extends BeanBase implements ITableCellRender
30
31
  ) {
31
32
  const { $host } = renderContext;
32
33
  return (
33
- <button
34
+ <ZButton
34
35
  class={options.class}
35
- type="button"
36
- onClick={async () => {
36
+ onPerform={async () => {
37
37
  const confirmed = await $host.$performCommand('basic-commands:confirm', {
38
38
  text: this.scope.locale.DeleteConfirm(),
39
39
  });
@@ -46,7 +46,7 @@ export class TableCellActionDelete extends BeanBase implements ITableCellRender
46
46
  }}
47
47
  >
48
48
  <ZIcon name="::delete" width={24}></ZIcon>
49
- </button>
49
+ </ZButton>
50
50
  );
51
51
  }
52
52
  }
@@ -58,7 +58,11 @@ export class ControllerBlockPage<TData extends {} = {}> extends BeanControllerBa
58
58
  pageSize: 20,
59
59
  };
60
60
 
61
- static $componentOptions: IComponentOptions = { inheritAttrs: false, deepExtendDefault: true };
61
+ static $componentOptions: IComponentOptions = {
62
+ inheritAttrs: false,
63
+ deepExtendDefault: true,
64
+ boundary: { retry: true },
65
+ };
62
66
 
63
67
  tableRef: BeanControllerTableBase<TData> | undefined;
64
68
  private _tableMetaRefreshPending = false;
@@ -42,7 +42,11 @@ export interface ControllerBlockPageEntryProps extends IResourceBlockOptionsBase
42
42
  @Controller()
43
43
  export class ControllerBlockPageEntry<TData extends {} = {}> extends BeanControllerBase {
44
44
  static $propsDefault = { pageTitleKey: 'name' };
45
- static $componentOptions: IComponentOptions = { inheritAttrs: false, deepExtendDefault: true };
45
+ static $componentOptions: IComponentOptions = {
46
+ inheritAttrs: false,
47
+ deepExtendDefault: true,
48
+ boundary: { retry: true },
49
+ };
46
50
 
47
51
  entryIdCreated?: TableIdentity;
48
52
 
@@ -51,7 +51,8 @@
51
51
  "title": "basic-table",
52
52
  "zovaModule": {
53
53
  "dependencies": {
54
- "basic-app": "5.0.0"
54
+ "basic-app": "5.0.0",
55
+ "basic-button": "5.0.0"
55
56
  }
56
57
  }
57
58
  }
@@ -8,6 +8,7 @@ import type {
8
8
  import { BeanBase } from 'zova';
9
9
  import { ZIcon } from 'zova-module-a-icon';
10
10
  import { TableCell } from 'zova-module-a-table';
11
+ import { ZButton } from 'zova-module-basic-button';
11
12
 
12
13
  declare module 'zova-module-a-openapi' {
13
14
  export interface IResourceTableActionRowRecord {
@@ -28,10 +29,9 @@ export class TableCellActionDelete extends BeanBase implements ITableCellRender
28
29
  ) {
29
30
  const { $host } = renderContext;
30
31
  return (
31
- <button
32
+ <ZButton
32
33
  class={options.class}
33
- type="button"
34
- onClick={async () => {
34
+ onPerform={async () => {
35
35
  const confirmed = await $host.$performCommand('basic-commands:confirm', {
36
36
  text: this.scope.locale.DeleteConfirm(),
37
37
  });
@@ -40,7 +40,7 @@ export class TableCellActionDelete extends BeanBase implements ITableCellRender
40
40
  }}
41
41
  >
42
42
  <ZIcon name="::delete" width={24}></ZIcon>
43
- </button>
43
+ </ZButton>
44
44
  );
45
45
  }
46
46
  }
@@ -6,6 +6,8 @@ import type {
6
6
 
7
7
  import { BeanControllerBase, Use } from 'zova';
8
8
  import { Controller } from 'zova-module-a-bean';
9
+ import { ZIcon } from 'zova-module-a-icon';
10
+ import { ZButton } from 'zova-module-basic-button';
9
11
 
10
12
  declare module 'zova-module-a-openapi' {
11
13
  export interface IResourceTableActionBulkRecord {
@@ -26,17 +28,17 @@ export class ControllerActionRefresh extends BeanControllerBase {
26
28
  protected render() {
27
29
  const disabled = this.$props.disabled === true || this.$props.dynamicDisabled === true;
28
30
  return (
29
- <button
30
- class={this.$props.class}
31
- type="button"
31
+ <ZButton
32
+ class={[this.$props.class, 'btn-square']}
32
33
  disabled={disabled}
33
- onClick={async () => {
34
- if (disabled) return;
35
- await this.$$renderContext.$$page.queryData.refetch();
34
+ aria-label={this.scope.locale.Refresh()}
35
+ title={this.scope.locale.Refresh()}
36
+ onPerform={async () => {
37
+ await this.$$renderContext.$$page.queryData.refetch({ throwOnError: true });
36
38
  }}
37
39
  >
38
- {this.scope.locale.Refresh() as string}
39
- </button>
40
+ <ZIcon name="::arrow-repeat" width={20}></ZIcon>
41
+ </ZButton>
40
42
  );
41
43
  }
42
44
  }
@@ -9,6 +9,7 @@
9
9
  "dependencies": {
10
10
  "zova-module-basic-adapter": "^5.0.5",
11
11
  "zova-module-basic-app": "^5.0.0",
12
+ "zova-module-basic-button": "^5.0.0",
12
13
  "zova-module-basic-captcha": "^5.0.8",
13
14
  "zova-module-basic-commands": "^5.1.15",
14
15
  "zova-module-basic-commandssync": "^5.0.5",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-module-a-layoutprofile",
3
- "version": "5.0.2",
3
+ "version": "5.0.3",
4
4
  "description": "",
5
5
  "keywords": [
6
6
  "Zova Module"
@@ -36,9 +36,6 @@ export class ModelLayoutProfile extends BeanModelBase {
36
36
  query: { layoutKey: this.layoutKey },
37
37
  });
38
38
  },
39
- meta: {
40
- disableErrorEffect: true,
41
- },
42
39
  });
43
40
  }
44
41
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-module-rest-resource",
3
- "version": "5.1.54",
3
+ "version": "5.1.55",
4
4
  "gitHead": "4dece229b6d3f4735930e833afcb4b95ec51bf86",
5
5
  "description": "",
6
6
  "keywords": [
@@ -9,4 +9,9 @@ export namespace NSControllerPageResource {
9
9
  export type ParamsOutput = z.output<typeof ControllerPageResourceSchemaParams>;
10
10
  }
11
11
 
12
- export const ZPageResource = createZovaComponentPage(ControllerPageResource, undefined, undefined);
12
+ export const ZPageResource = createZovaComponentPage(
13
+ ControllerPageResource,
14
+ undefined,
15
+ undefined,
16
+ ControllerPageResource.$componentOptions,
17
+ );
@@ -28,7 +28,6 @@ interface IModelResourceQueryItemOptions<TData> {
28
28
  queryFn: () => Promise<TData>;
29
29
  meta?: {
30
30
  disableSuspenseOnInit?: boolean;
31
- disableErrorEffect?: boolean;
32
31
  persister?: QueryMetaPersister | boolean;
33
32
  };
34
33
  }
@@ -1,6 +1,6 @@
1
1
  import { VNode } from 'vue';
2
2
  import { z } from 'zod';
3
- import { BeanControllerPageBase, deepExtend, Use, usePrepareArg } from 'zova';
3
+ import { BeanControllerPageBase, deepExtend, IComponentOptions, Use, usePrepareArg } from 'zova';
4
4
  import { ZovaJsx } from 'zova-jsx';
5
5
  import { Controller } from 'zova-module-a-bean';
6
6
  import { $QueryEnsureLoaded } from 'zova-module-a-model';
@@ -14,6 +14,10 @@ export const ControllerPageResourceSchemaParams = z.object({
14
14
 
15
15
  @Controller()
16
16
  export class ControllerPageResource extends BeanControllerPageBase {
17
+ static $componentOptions: IComponentOptions = {
18
+ boundary: { retry: true },
19
+ };
20
+
17
21
  jsxZova: ZovaJsx;
18
22
 
19
23
  @Use({ beanFullName: 'rest-resource.model.resource' })
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "zova-suite-a-cabloy",
3
- "version": "5.1.56",
3
+ "version": "5.1.57",
4
4
  "gitHead": "4dece229b6d3f4735930e833afcb4b95ec51bf86",
5
5
  "description": "",
6
6
  "author": "",
7
7
  "files": [],
8
8
  "type": "module",
9
9
  "dependencies": {
10
- "zova-module-a-layoutprofile": "^5.0.2",
10
+ "zova-module-a-layoutprofile": "^5.0.3",
11
11
  "zova-module-a-rbac": "^5.0.1",
12
- "zova-module-rest-resource": "^5.1.54"
12
+ "zova-module-rest-resource": "^5.1.55"
13
13
  },
14
14
  "title": "a-cabloy"
15
15
  }
@@ -22,6 +22,7 @@ export async function generateFilePage(
22
22
  importStyleFirst,
23
23
  hasStyleFirst,
24
24
  classNameStyleFirst,
25
+ hasComponentOptions,
25
26
  } = controllerInfo;
26
27
  const { nameSchemaParams, hasSchemaParams, nameSchemaQuery, hasSchemaQuery } = controllerInfo;
27
28
  const contentImports: string[] = [];
@@ -68,7 +69,8 @@ export async function generateFilePage(
68
69
  }`);
69
70
  }
70
71
  // page
71
- const contentPage = `export const ZPage${nameCapitalize} = createZovaComponentPage(${className}, ${hasRenderFirst ? classNameRenderFirst : undefined}, ${hasStyleFirst ? classNameStyleFirst : undefined});`;
72
+ const componentOptions = hasComponentOptions ? `${className}.$componentOptions` : undefined;
73
+ const contentPage = `export const ZPage${nameCapitalize} = createZovaComponentPage(${className}, ${hasRenderFirst ? classNameRenderFirst : undefined}, ${hasStyleFirst ? classNameStyleFirst : undefined}${componentOptions ? `, ${componentOptions}` : ''});`;
72
74
  // content
73
75
  const content = `${contentImports.join('\n')}
74
76
  ${combineContentRenderAndStyle(controllerInfo, moduleName, className, '', '')}
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-module-a-bean",
3
- "version": "5.1.35",
3
+ "version": "5.1.36",
4
4
  "gitHead": "09d901d17140a80ee0764211b441cda72fd94663",
5
5
  "description": "",
6
6
  "keywords": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-module-a-model",
3
- "version": "5.1.36",
3
+ "version": "5.1.37",
4
4
  "gitHead": "09d901d17140a80ee0764211b441cda72fd94663",
5
5
  "description": "core",
6
6
  "keywords": [
@@ -41,17 +41,7 @@ export class BeanModelUseMutation extends BeanModelUseStateGeneral {
41
41
  mutationKey = this.self._forceQueryKeyPrefix(mutationKey);
42
42
  const mutationHash = hashKey(mutationKey);
43
43
  if (!this[SymbolUseMutations][mutationHash]) {
44
- const optionsDefault: any = {};
45
- if (!cast(mutationOptions).meta?.disableErrorEffect) {
46
- optionsDefault.onError = (error, variables, context) => {
47
- let errorInfo = cast(mutationOptions).meta?.errorInfo;
48
- if (typeof errorInfo === 'function') {
49
- errorInfo = errorInfo(error, variables, context);
50
- }
51
- this.$errorHandler(error, errorInfo ?? 'useMutationData');
52
- };
53
- }
54
- mutationOptions = Object.assign(optionsDefault, mutationOptions, { mutationKey });
44
+ mutationOptions = Object.assign({}, mutationOptions, { mutationKey });
55
45
  this[SymbolUseMutations][mutationHash] = this.$useMutation(mutationOptions, queryClient);
56
46
  }
57
47
  return this[SymbolUseMutations][mutationHash] as UnwrapNestedRefs<
@@ -1,8 +1,6 @@
1
1
  import type { DefaultError, QueryClient, QueryKey, UseQueryOptions } from '@tanstack/vue-query';
2
2
 
3
- import { checkErrorJwtExpired } from '@cabloy/utils';
4
3
  import { useQuery } from '@tanstack/vue-query';
5
- import { cast } from 'zova';
6
4
 
7
5
  import type {
8
6
  DefinedInitialQueryOptions,
@@ -45,21 +43,7 @@ export class BeanModelUseQuery extends BeanModelQuery {
45
43
  $useQuery(options, queryClient) {
46
44
  const queryKey = this.self._forceQueryKeyPrefix(options.queryKey);
47
45
  const persister = this._createPersister(options.meta?.persister);
48
- const optionsDefault: any = {};
49
- if (!cast(options).meta?.disableErrorEffect) {
50
- optionsDefault.throwOnError = (error: Error, query) => {
51
- let errorInfo = cast(options).meta?.errorInfo;
52
- if (typeof errorInfo === 'function') {
53
- errorInfo = errorInfo(error, query);
54
- }
55
- if (!error.message.includes('useQuery:') && !checkErrorJwtExpired(error)) {
56
- error.message = `useQuery: [${queryKey.join(', ')}]: ${error.message}`;
57
- }
58
- this.$errorHandler(error, errorInfo ?? 'useQuery');
59
- return false;
60
- };
61
- }
62
- options = Object.assign(optionsDefault, options, { queryKey, persister });
46
+ options = Object.assign({}, options, { queryKey, persister });
63
47
  // staleTime
64
48
  const sync = typeof options.meta?.persister === 'object' && options.meta?.persister?.sync;
65
49
  if (sync !== true) {
@@ -68,7 +68,8 @@ async function _QueryEnsureLoadedInner<T = any>(fn?: TypeQueryEnsureLoadedFn<T>)
68
68
  if (!fn) return;
69
69
  const query = fn();
70
70
  if (query && query.data === undefined) {
71
- await query.suspense();
71
+ const result = await query.suspense();
72
+ if (result.error) throw result.error;
72
73
  }
73
74
  return query;
74
75
  }
@@ -59,17 +59,12 @@ export interface MyQueryMetaBase extends Record<string, unknown> {
59
59
  defaultData?: (() => any) | any;
60
60
  ssr?: QueryMetaSSR;
61
61
  persister?: QueryMetaPersister | boolean;
62
- disableErrorEffect?: boolean;
63
62
  disableSuspenseOnInit?: boolean;
64
63
  }
65
64
 
66
- export interface MyQueryMeta extends MyQueryMetaBase {
67
- errorInfo?: ((error, query) => string) | string;
68
- }
65
+ export interface MyQueryMeta extends MyQueryMetaBase {}
69
66
 
70
- export interface MyMutationMeta extends MyQueryMetaBase {
71
- errorInfo?: ((error, variables, context) => string) | string;
72
- }
67
+ export interface MyMutationMeta extends Record<string, unknown> {}
73
68
 
74
69
  export interface QueryMetaSSR {
75
70
  /** default is true */
@@ -0,0 +1,14 @@
1
+ import type { MutationObserverOptions } from '@tanstack/vue-query';
2
+
3
+ import type { MyMutationMeta } from '../../src/types/query.ts';
4
+
5
+ const mutationOptions: MutationObserverOptions<number, Error, { id: number }> = {
6
+ mutationKey: ['test:mutation'],
7
+ mutationFn: async variables => variables.id,
8
+ onError: (_error, _variables, _context) => {},
9
+ };
10
+
11
+ const mutationMeta: MyMutationMeta = { source: 'test' };
12
+
13
+ void mutationOptions;
14
+ void mutationMeta;
@@ -0,0 +1,56 @@
1
+ import assert from 'node:assert/strict';
2
+ import test from 'node:test';
3
+
4
+ import { $QueryEnsureLoaded } from '../../src/lib/utils.js';
5
+
6
+ type QueryResult<T> = {
7
+ data: T | undefined;
8
+ error: Error | null;
9
+ };
10
+
11
+ function createQuery<T>(data: T | undefined, result: QueryResult<T>) {
12
+ let suspenseCalls = 0;
13
+ const query = {
14
+ data,
15
+ suspense: async () => {
16
+ suspenseCalls++;
17
+ return result;
18
+ },
19
+ };
20
+ return {
21
+ query,
22
+ get suspenseCalls() {
23
+ return suspenseCalls;
24
+ },
25
+ };
26
+ }
27
+
28
+ test('QueryEnsureLoaded throws an initial query error', async () => {
29
+ const error = new Error('Initial query load failed');
30
+ const fixture = createQuery(undefined, { data: undefined, error });
31
+
32
+ await assert.rejects(
33
+ $QueryEnsureLoaded(() => fixture.query as never),
34
+ caught => caught === error,
35
+ );
36
+ assert.equal(fixture.suspenseCalls, 1);
37
+ });
38
+
39
+ test('QueryEnsureLoaded accepts cached data when a background refresh has failed', async () => {
40
+ const error = new Error('Background refresh failed');
41
+ const fixture = createQuery({ value: 'cached' }, { data: { value: 'cached' }, error });
42
+
43
+ const result = await $QueryEnsureLoaded(() => fixture.query as never);
44
+
45
+ assert.equal(result, fixture.query);
46
+ assert.equal(fixture.suspenseCalls, 0);
47
+ });
48
+
49
+ test('QueryEnsureLoaded returns a query after an initial successful load', async () => {
50
+ const fixture = createQuery(undefined, { data: { value: 'loaded' }, error: null });
51
+
52
+ const result = await $QueryEnsureLoaded(() => fixture.query as never);
53
+
54
+ assert.equal(result, fixture.query);
55
+ assert.equal(fixture.suspenseCalls, 1);
56
+ });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-module-a-router",
3
- "version": "5.1.37",
3
+ "version": "5.1.38",
4
4
  "gitHead": "09d901d17140a80ee0764211b441cda72fd94663",
5
5
  "description": "router",
6
6
  "keywords": [
@@ -238,6 +238,9 @@ export class Monkey
238
238
  private _ssrErrorHandler() {
239
239
  if (!process.env.CLIENT) return;
240
240
  this.app.meta.event.on('app:errorHandler', (data, next) => {
241
+ if (_isLoadErrorHandledClient(data)) {
242
+ data.load!.disposition = 'handled';
243
+ }
241
244
  const err = next();
242
245
  if (!err || !(err instanceof Error)) return err;
243
246
  return this._errorHandlerDefaultClient(err, data);
@@ -265,3 +268,8 @@ export class Monkey
265
268
  return err;
266
269
  }
267
270
  }
271
+
272
+ function _isLoadErrorHandledClient(data: IErrorHandlerEventData) {
273
+ if (data.load?.kind !== 'controller-load') return false;
274
+ return [301, 302, 401, 600].includes(Number((data.err as ErrorSSR).code));
275
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-module-a-ssr",
3
- "version": "5.1.39",
3
+ "version": "5.1.40",
4
4
  "gitHead": "09d901d17140a80ee0764211b441cda72fd94663",
5
5
  "description": "",
6
6
  "keywords": [
@@ -1,5 +1,5 @@
1
1
  import type { ComponentInternalInstance, Ref, VNode } from 'vue';
2
- import type { Functionable, ILocaleRecord, ZovaContext } from 'zova';
2
+ import type { Functionable, IControllerLoadEvent, ILocaleRecord, ZovaContext } from 'zova';
3
3
 
4
4
  import { RouteLocationNormalizedGeneric, RouteLocationResolvedGeneric } from '@cabloy/vue-router';
5
5
  import { includeBooleanAttr, isBooleanAttr, isString, stringifyStyle } from '@vue/shared';
@@ -14,6 +14,7 @@ import type {
14
14
  OnHydratePropHasMismatch,
15
15
  OnHydratePropHasMismatchResult,
16
16
  SSRContext,
17
+ SSRControllerLoadErrorSnapshot,
17
18
  TypeSsrProfile,
18
19
  TypeSsrSitePerformAction,
19
20
  } from '../types/ssr.js';
@@ -40,6 +41,7 @@ export class CtxSSR extends BeanSimple {
40
41
 
41
42
  private [SymbolHydratingCounter]: number = 0;
42
43
  private [SymbolServerContexts]: Set<ZovaContext> = new Set();
44
+ private _controllerLoadSequence: Record<string, number> = {};
43
45
 
44
46
  public metaStore: CtxSSRMetaStore;
45
47
 
@@ -132,6 +134,53 @@ export class CtxSSR extends BeanSimple {
132
134
  }
133
135
  }
134
136
 
137
+ /** @internal */
138
+ public controllerLoad(event: IControllerLoadEvent) {
139
+ if (event.phase === 'prepare') {
140
+ if (process.env.SERVER) {
141
+ event.ctx.meta.state.controllerLoadKey = this._allocateControllerLoadKey(event);
142
+ return;
143
+ }
144
+ if (!process.env.CLIENT || !this.isRuntimeSsrPreHydration) return;
145
+ const key = this._allocateControllerLoadKey(event);
146
+ const snapshot = this.stateDefer.controllerLoad?.[key];
147
+ if (
148
+ !snapshot ||
149
+ snapshot.version !== 1 ||
150
+ snapshot.controllerBeanName !== event.controllerBeanName
151
+ ) {
152
+ return;
153
+ }
154
+ delete this.stateDefer.controllerLoad![key];
155
+ event.replayError = _createControllerLoadError(snapshot);
156
+ event.replayControllerRecorded = snapshot.controllerRecorded;
157
+ return;
158
+ }
159
+ if (event.phase !== 'fallback' || !process.env.SERVER || !event.error) return;
160
+ const key = event.ctx.meta.state.controllerLoadKey;
161
+ if (!key) return;
162
+ const controllerLoad = (this.stateDefer.controllerLoad ??= {});
163
+ controllerLoad[key] = _createControllerLoadErrorSnapshot(event);
164
+ }
165
+
166
+ private _allocateControllerLoadKey(event: IControllerLoadEvent) {
167
+ const instance = event.ctx.instance;
168
+ const parts: string[] = [];
169
+ let current: ComponentInternalInstance | null = instance;
170
+ while (current) {
171
+ const componentType = current.type as any;
172
+ const name =
173
+ componentType.__file ?? componentType.name ?? componentType.__name ?? 'anonymous';
174
+ const key = current.vnode.key ?? '';
175
+ parts.unshift(`${name}:${String(key)}`);
176
+ current = current.parent;
177
+ }
178
+ const structuralKey = parts.join('/');
179
+ const occurrence = this._controllerLoadSequence[structuralKey] ?? 0;
180
+ this._controllerLoadSequence[structuralKey] = occurrence + 1;
181
+ return `${structuralKey}#${occurrence}:${event.controllerBeanName ?? ''}`;
182
+ }
183
+
135
184
  get profile(): TypeSsrProfile {
136
185
  return resolveSsrProfile(this.state.ssrProfile, this.sys.env.SSR_PROFILE);
137
186
  }
@@ -367,3 +416,38 @@ export class CtxSSR extends BeanSimple {
367
416
  this._serverContextsDispose();
368
417
  }
369
418
  }
419
+
420
+ function _createControllerLoadErrorSnapshot(
421
+ event: IControllerLoadEvent,
422
+ ): SSRControllerLoadErrorSnapshot {
423
+ const error = event.error! as any;
424
+ const snapshot: SSRControllerLoadErrorSnapshot = {
425
+ version: 1,
426
+ controllerBeanName: event.controllerBeanName,
427
+ name: error.name || 'Error',
428
+ message: error.message || String(error),
429
+ controllerRecorded: event.controllerRecorded === true,
430
+ };
431
+ for (const key of ['code', 'status']) {
432
+ if (typeof error[key] === 'number' || typeof error[key] === 'string') {
433
+ snapshot[key] = error[key];
434
+ }
435
+ }
436
+ for (const key of ['pagePath', 'url']) {
437
+ if (typeof error[key] === 'string') {
438
+ snapshot[key] = error[key];
439
+ }
440
+ }
441
+ return snapshot;
442
+ }
443
+
444
+ function _createControllerLoadError(snapshot: SSRControllerLoadErrorSnapshot) {
445
+ const error = new Error(snapshot.message);
446
+ error.name = snapshot.name;
447
+ for (const key of ['code', 'status', 'pagePath', 'url']) {
448
+ if (snapshot[key] !== undefined) {
449
+ (error as any)[key] = snapshot[key];
450
+ }
451
+ }
452
+ return error;
453
+ }