cabloy 5.1.172 → 5.1.173

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 (94) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +6 -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/zova/package.original.json +1 -1
  14. package/zova/packages-cli/cli/package.json +2 -2
  15. package/zova/packages-cli/cli-set-front/package.json +3 -3
  16. package/zova/packages-utils/babel-plugin-zova-behavior/package.json +2 -1
  17. package/zova/packages-utils/babel-plugin-zova-behavior/src/index.ts +21 -8
  18. package/zova/packages-utils/babel-plugin-zova-behavior/test/behaviorSlots.test.ts +111 -0
  19. package/zova/packages-utils/zova-jsx/package.json +2 -2
  20. package/zova/packages-utils/zova-vite/package.json +2 -2
  21. package/zova/packages-zova/zova/package.json +3 -3
  22. package/zova/packages-zova/zova-core/package.json +1 -1
  23. package/zova/packages-zova/zova-core/src/bean/beanBase.ts +1 -1
  24. package/zova/packages-zova/zova-core/src/bean/beanContainer.ts +95 -39
  25. package/zova/packages-zova/zova-core/src/bean/resource/error/type.ts +9 -0
  26. package/zova/packages-zova/zova-core/src/bean/type.ts +18 -0
  27. package/zova/packages-zova/zova-core/src/components/component.ts +2 -1
  28. package/zova/packages-zova/zova-core/src/composables/useController.ts +155 -25
  29. package/zova/packages-zova/zova-core/src/core/component/error.ts +62 -5
  30. package/zova/packages-zova/zova-core/src/core/context/component.ts +37 -2
  31. package/zova/packages-zova/zova-core/src/core/context/hooks.ts +13 -2
  32. package/zova/packages-zova/zova-core/src/core/context/meta.ts +1 -0
  33. package/zova/packages-zova/zova-core/src/core/context/state.ts +161 -0
  34. package/zova/packages-zova/zova-core/src/core/sys/config.ts +32 -7
  35. package/zova/packages-zova/zova-core/src/types/interface/monkey.ts +15 -0
  36. package/zova/packages-zova/zova-core/src/types/utils/powerPartial.ts +1 -1
  37. package/zova/pnpm-lock.yaml +69 -54
  38. package/zova/src/front/config/config/config.ts +11 -0
  39. package/zova/src/front/config/config/render.tsx +79 -0
  40. package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +0 -1
  41. package/zova/src/suite/a-demo/modules/demo-basic/src/.metadata/component/controllerBoundaryProbe.ts +31 -0
  42. package/zova/src/suite/a-demo/modules/demo-basic/src/.metadata/component/controllerBoundaryProbeInline.ts +34 -0
  43. package/zova/src/suite/a-demo/modules/demo-basic/src/.metadata/component/controllerBoundaryProbeOverride.ts +34 -0
  44. package/zova/src/suite/a-demo/modules/demo-basic/src/.metadata/index.ts +58 -1
  45. package/zova/src/suite/a-demo/modules/demo-basic/src/.metadata/page/controllerBoundary.ts +23 -0
  46. package/zova/src/suite/a-demo/modules/demo-basic/src/component/controllerBoundaryProbe/controller.tsx +36 -0
  47. package/zova/src/suite/a-demo/modules/demo-basic/src/component/controllerBoundaryProbeInline/controller.tsx +34 -0
  48. package/zova/src/suite/a-demo/modules/demo-basic/src/component/controllerBoundaryProbeOverride/controller.tsx +46 -0
  49. package/zova/src/suite/a-demo/modules/demo-basic/src/page/controllerBoundary/controller.tsx +185 -0
  50. package/zova/src/suite/a-demo/modules/demo-basic/src/routes.ts +6 -0
  51. package/zova/src/suite/a-home/modules/home-base/src/config/locale/en-us.ts +1 -0
  52. package/zova/src/suite/a-home/modules/home-base/src/config/locale/zh-cn.ts +1 -0
  53. package/zova/src/suite/a-training/modules/training-student/src/model/student.ts +0 -3
  54. package/zova/src/suite/cabloy-basic/modules/basic-button/package.json +58 -0
  55. package/zova/src/suite/cabloy-basic/modules/basic-button/src/.metadata/component/button.ts +31 -0
  56. package/zova/src/suite/cabloy-basic/modules/basic-button/src/.metadata/index.ts +122 -0
  57. package/zova/src/suite/cabloy-basic/modules/basic-button/src/.metadata/this.ts +2 -0
  58. package/zova/src/suite/cabloy-basic/modules/basic-button/src/bean/behavior.perform.tsx +103 -0
  59. package/zova/src/suite/cabloy-basic/modules/basic-button/src/component/button/controller.tsx +39 -0
  60. package/zova/src/suite/cabloy-basic/modules/basic-button/src/index.ts +1 -0
  61. package/zova/src/suite/cabloy-basic/modules/basic-button/tsconfig.build.json +13 -0
  62. package/zova/src/suite/cabloy-basic/modules/basic-button/tsconfig.json +5 -0
  63. package/zova/src/suite/cabloy-basic/modules/basic-details/package.json +2 -1
  64. package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/tableCell.actionDelete.tsx +4 -4
  65. package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx +5 -1
  66. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx +5 -1
  67. package/zova/src/suite/cabloy-basic/modules/basic-table/package.json +2 -1
  68. package/zova/src/suite/cabloy-basic/modules/basic-table/src/bean/tableCell.actionDelete.tsx +4 -4
  69. package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/actionRefresh/controller.tsx +10 -8
  70. package/zova/src/suite/cabloy-basic/package.json +1 -0
  71. package/zova/src/suite-vendor/a-cabloy/modules/a-layoutprofile/package.json +1 -1
  72. package/zova/src/suite-vendor/a-cabloy/modules/a-layoutprofile/src/model/layoutprofile.ts +0 -3
  73. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  74. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/.metadata/page/resource.ts +6 -1
  75. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +0 -1
  76. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resource/controller.tsx +5 -1
  77. package/zova/src/suite-vendor/a-cabloy/package.json +3 -3
  78. package/zova/src/suite-vendor/a-zova/modules/a-bean/cli/controller/metadata/generateFilePage.ts +3 -1
  79. package/zova/src/suite-vendor/a-zova/modules/a-bean/package.json +1 -1
  80. package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
  81. package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useMutation.ts +1 -11
  82. package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useQuery.ts +1 -17
  83. package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/utils.ts +2 -1
  84. package/zova/src/suite-vendor/a-zova/modules/a-model/src/types/query.ts +2 -7
  85. package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/mutation.types.ts +14 -0
  86. package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/queryEnsureLoaded.test.ts +56 -0
  87. package/zova/src/suite-vendor/a-zova/modules/a-router/package.json +1 -1
  88. package/zova/src/suite-vendor/a-zova/modules/a-router/src/monkey.ts +8 -0
  89. package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
  90. package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssr.ts +85 -1
  91. package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/monkey.ts +25 -1
  92. package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/types/ssr.ts +15 -1
  93. package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
  94. package/zova/src/suite-vendor/a-zova/package.json +6 -6
@@ -92,13 +92,16 @@ function createVisitor(context: ContextInfo) {
92
92
  ),
93
93
  );
94
94
  // -> ()=>{}
95
- const children = (nodePath.container as any)?.children;
96
- if (children && children.length > 0 && !_checkIfHasJsxExpression(children)) {
97
- const expressionArray = t.arrayExpression();
98
- expressionArray.elements = (nodePath.container as any)?.children;
99
- const expressionSlot = t.arrowFunctionExpression([], expressionArray);
95
+ const children = (nodePath.container as t.JSXElement).children;
96
+ if (children.length > 0 && _shouldWrapChildrenInSlot(tag, children)) {
97
+ const expressionSlot = t.arrowFunctionExpression(
98
+ [],
99
+ t.arrayExpression([
100
+ t.jsxFragment(t.jsxOpeningFragment(), t.jsxClosingFragment(), children),
101
+ ]),
102
+ );
100
103
  const expressionSlotContainer = t.jsxExpressionContainer(expressionSlot);
101
- (nodePath.container as any).children = [expressionSlotContainer];
104
+ (nodePath.container as t.JSXElement).children = [expressionSlotContainer];
102
105
  }
103
106
  }
104
107
  }
@@ -106,8 +109,18 @@ function createVisitor(context: ContextInfo) {
106
109
  };
107
110
  }
108
111
 
109
- function _checkIfHasJsxExpression(children: t.JSXElement[]) {
110
- return children.some(node => t.isJSXExpressionContainer(node));
112
+ function _shouldWrapChildrenInSlot(tag: t.Expression, children: t.JSXElement['children']) {
113
+ if (_hasExplicitDefaultSlotFunction(children)) return false;
114
+ return t.isStringLiteral(tag) || !children.some(child => t.isJSXExpressionContainer(child));
115
+ }
116
+
117
+ function _hasExplicitDefaultSlotFunction(children: t.JSXElement['children']) {
118
+ if (children.length !== 1) return false;
119
+ const [child] = children;
120
+ return (
121
+ t.isJSXExpressionContainer(child) &&
122
+ (t.isArrowFunctionExpression(child.expression) || t.isFunctionExpression(child.expression))
123
+ );
111
124
  }
112
125
 
113
126
  // bs-providerId-moduleName-beanName
@@ -0,0 +1,111 @@
1
+ import { transformSync, traverse, types as t } from '@babel/core';
2
+ import vueJsx from '@vue/babel-plugin-jsx';
3
+ import { renderToString } from '@vue/server-renderer';
4
+ import assert from 'node:assert/strict';
5
+ import test from 'node:test';
6
+ import { createVNode, Fragment } from 'vue';
7
+
8
+ import behavior from '../src/index.ts';
9
+
10
+ function transform(source: string) {
11
+ const result = transformSync(source, {
12
+ filename: 'fixture.tsx',
13
+ configFile: false,
14
+ babelrc: false,
15
+ ast: true,
16
+ plugins: [behavior, vueJsx],
17
+ });
18
+ assert.ok(result?.ast);
19
+ return result.ast;
20
+ }
21
+
22
+ function getBehaviorVNodeChildren(ast: t.File) {
23
+ let children: t.Expression | null | undefined;
24
+ traverse(ast, {
25
+ CallExpression(path) {
26
+ if (!t.isIdentifier(path.node.callee, { name: '_createVNode' })) return;
27
+ const [tag, , vnodeChildren] = path.node.arguments;
28
+ if (!t.isCallExpression(tag)) return;
29
+ if (!t.isIdentifier(tag.callee, { name: '_resolveComponent' })) return;
30
+ if (!t.isStringLiteral(tag.arguments[0], { value: 'ZBehavior__' })) return;
31
+ children = vnodeChildren as t.Expression | null | undefined;
32
+ path.stop();
33
+ },
34
+ });
35
+ assert.notEqual(children, undefined, 'should render ZBehavior__');
36
+ return children;
37
+ }
38
+
39
+ function getDefaultSlot(children: t.Expression | null | undefined) {
40
+ assert.ok(t.isObjectExpression(children), 'ZBehavior__ children should be a slot object');
41
+ const defaultSlot = children.properties.find(
42
+ property =>
43
+ t.isObjectProperty(property) &&
44
+ t.isIdentifier(property.key, { name: 'default' }) &&
45
+ t.isArrowFunctionExpression(property.value),
46
+ );
47
+ assert.ok(defaultSlot && t.isObjectProperty(defaultSlot));
48
+ assert.ok(t.isArrowFunctionExpression(defaultSlot.value));
49
+ return defaultSlot.value;
50
+ }
51
+
52
+ function getSlotFragmentChildren(defaultSlot: t.ArrowFunctionExpression) {
53
+ assert.ok(t.isArrayExpression(defaultSlot.body));
54
+ assert.equal(defaultSlot.body.elements.length, 1);
55
+ const [fragment] = defaultSlot.body.elements;
56
+ assert.ok(t.isCallExpression(fragment));
57
+ const children = fragment.arguments[2];
58
+ assert.ok(t.isArrayExpression(children));
59
+ return children.elements;
60
+ }
61
+
62
+ test('native behavior target wraps a dynamic child in a default slot', () => {
63
+ const ast = transform(
64
+ 'const dom = <button bs-basic-button-perform={options}>{this.$slotDefault?.()}</button>;',
65
+ );
66
+ const defaultSlot = getDefaultSlot(getBehaviorVNodeChildren(ast));
67
+ const children = getSlotFragmentChildren(defaultSlot);
68
+ assert.equal(children.length, 1);
69
+ assert.ok(t.isOptionalCallExpression(children[0]));
70
+ });
71
+
72
+ test('native behavior target wraps static children in a default slot', () => {
73
+ const ast = transform('const dom = <button bs-basic-button-perform={options}>Delete</button>;');
74
+ const defaultSlot = getDefaultSlot(getBehaviorVNodeChildren(ast));
75
+ assert.equal(getSlotFragmentChildren(defaultSlot).length, 1);
76
+ });
77
+
78
+ test('native behavior target preserves an explicit default slot function', () => {
79
+ const ast = transform(
80
+ 'const dom = <button bs-basic-button-perform={options}>{() => this.$slotDefault?.()}</button>;',
81
+ );
82
+ const defaultSlot = getDefaultSlot(getBehaviorVNodeChildren(ast));
83
+ assert.ok(t.isOptionalCallExpression(defaultSlot.body));
84
+ });
85
+
86
+ test('native behavior target without children does not create a default slot', () => {
87
+ const ast = transform('const dom = <button bs-basic-button-perform={options} />;');
88
+ assert.ok(t.isNullLiteral(getBehaviorVNodeChildren(ast)));
89
+ });
90
+
91
+ test('component behavior target keeps dynamic children as a default slot', () => {
92
+ const ast = transform(
93
+ 'const dom = <VBtn bs-start-button-perform={options}>{this.$slotDefault?.()}</VBtn>;',
94
+ );
95
+ const defaultSlot = getDefaultSlot(getBehaviorVNodeChildren(ast));
96
+ assert.ok(t.isArrayExpression(defaultSlot.body));
97
+ assert.equal(defaultSlot.body.elements.length, 1);
98
+ assert.ok(t.isOptionalCallExpression(defaultSlot.body.elements[0]));
99
+ });
100
+
101
+ test('native targets render a default slot that returns an array', async () => {
102
+ const fragment = createVNode(Fragment, null, ['Delete']);
103
+ const html = await renderToString(createVNode('button', null, { default: () => [fragment] }));
104
+ assert.match(html, /Delete/);
105
+ });
106
+
107
+ test('native targets do not render a default slot that returns a bare Fragment', async () => {
108
+ const fragment = createVNode(Fragment, null, ['Delete']);
109
+ const html = await renderToString(createVNode('button', null, { default: () => fragment }));
110
+ assert.doesNotMatch(html, /Delete/);
111
+ });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-jsx",
3
- "version": "1.1.89",
3
+ "version": "1.1.90",
4
4
  "gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
5
5
  "description": "Zova JSX",
6
6
  "keywords": [
@@ -50,7 +50,7 @@
50
50
  "@cabloy/word-utils": "^2.1.14",
51
51
  "typestyle": "^2.4.0",
52
52
  "vue": "^3.5.38",
53
- "zova-core": "^5.1.89"
53
+ "zova-core": "^5.1.90"
54
54
  },
55
55
  "devDependencies": {
56
56
  "clean-package": "^2.2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-vite",
3
- "version": "1.1.45",
3
+ "version": "1.1.46",
4
4
  "gitHead": "09d901d17140a80ee0764211b441cda72fd94663",
5
5
  "description": "zova vite",
6
6
  "keywords": [
@@ -58,7 +58,7 @@
58
58
  "babel-plugin-transform-typescript-metadata": "^0.4.0",
59
59
  "babel-plugin-zova-bean-module": "^1.2.15",
60
60
  "babel-plugin-zova-bean-use": "^1.1.15",
61
- "babel-plugin-zova-behavior": "^1.1.15",
61
+ "babel-plugin-zova-behavior": "^1.1.16",
62
62
  "babel-plugin-zova-component": "^1.1.15",
63
63
  "chalk": "^3.0.0",
64
64
  "esbuild": "^0.25.12",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova",
3
- "version": "5.1.162",
3
+ "version": "5.1.163",
4
4
  "gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
5
5
  "description": "A vue3 framework with ioc",
6
6
  "keywords": [
@@ -45,8 +45,8 @@
45
45
  "postpack": "clean-package restore"
46
46
  },
47
47
  "dependencies": {
48
- "zova-core": "^5.1.89",
49
- "zova-suite-a-zova": "^5.1.161"
48
+ "zova-core": "^5.1.90",
49
+ "zova-suite-a-zova": "^5.1.162"
50
50
  },
51
51
  "devDependencies": {
52
52
  "clean-package": "^2.2.0",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-core",
3
- "version": "5.1.89",
3
+ "version": "5.1.90",
4
4
  "gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
5
5
  "description": "A vue3 framework with ioc",
6
6
  "keywords": [
@@ -192,7 +192,7 @@ export class BeanBase extends BeanBaseSimple {
192
192
 
193
193
  protected $controllerMounted(fn: any) {
194
194
  return this.ctx.util.instanceScope(() => {
195
- return this.ctx.meta.hooks.onMounted(fn);
195
+ return this.ctx.meta.hooks.onMounted(fn, this);
196
196
  });
197
197
  }
198
198
 
@@ -74,50 +74,90 @@ export class BeanContainer {
74
74
  /** @internal */
75
75
  public dispose() {
76
76
  const beanInstances = this[SymbolBeanContainerInstances];
77
- for (const prop in beanInstances) {
78
- if (prop.startsWith('$$')) continue;
79
- const beanInstance = cast(beanInstances[prop]);
80
- if (beanInstance && !(beanInstance instanceof BeanAopBase) && beanInstance.__dispose__) {
81
- if (this.containerType === 'sys') {
82
- this.sys.meta.module._monkeyModuleSync(
83
- false,
84
- 'beanDispose',
85
- undefined,
86
- this,
87
- beanInstance,
88
- );
89
- beanInstance.__dispose__();
90
- this.sys.meta.module._monkeyModuleSync(
91
- false,
92
- 'beanDisposed',
93
- undefined,
94
- this,
95
- beanInstance,
96
- );
97
- } else {
98
- this.app.meta.module._monkeyModuleSync(
99
- false,
100
- 'beanDispose',
101
- undefined,
102
- this,
103
- beanInstance,
104
- );
77
+ let error: Error | undefined;
78
+ try {
79
+ for (const prop in beanInstances) {
80
+ if (prop.startsWith('$$')) continue;
81
+ const beanInstance = cast(beanInstances[prop]);
82
+ if (!beanInstance || beanInstance instanceof BeanAopBase || !beanInstance.__dispose__)
83
+ continue;
84
+ try {
85
+ if (this.containerType === 'sys') {
86
+ this.sys.meta.module._monkeyModuleSync(
87
+ false,
88
+ 'beanDispose',
89
+ undefined,
90
+ this,
91
+ beanInstance,
92
+ );
93
+ beanInstance.__dispose__();
94
+ this.sys.meta.module._monkeyModuleSync(
95
+ false,
96
+ 'beanDisposed',
97
+ undefined,
98
+ this,
99
+ beanInstance,
100
+ );
101
+ } else {
102
+ this.app.meta.module._monkeyModuleSync(
103
+ false,
104
+ 'beanDispose',
105
+ undefined,
106
+ this,
107
+ beanInstance,
108
+ );
109
+ this.runWithInstanceScopeOrAppContext(() => {
110
+ return beanInstance.__dispose__();
111
+ });
112
+ this.app.meta.module._monkeyModuleSync(
113
+ false,
114
+ 'beanDisposed',
115
+ undefined,
116
+ this,
117
+ beanInstance,
118
+ );
119
+ }
120
+ } catch (err) {
121
+ error ??= err as Error;
122
+ }
123
+ }
124
+ } finally {
125
+ this[SymbolBeanContainerInstances] = shallowReactive({});
126
+ this[SymbolBeanContainerParent] = undefined;
127
+ this[SymbolGetBeanSelectorInnerPromises] = {};
128
+ }
129
+ // eslint-disable no-throw-literal
130
+ if (error) throw error;
131
+ }
132
+
133
+ /** @internal */
134
+ public retryReset(ownedBeans: Set<object>) {
135
+ const beanInstances = this[SymbolBeanContainerInstances];
136
+ let error: Error | undefined;
137
+ try {
138
+ for (const prop in beanInstances) {
139
+ const beanInstance = cast(beanInstances[prop]);
140
+ if (!ownedBeans.has(beanInstance)) continue;
141
+ if (!beanInstance || beanInstance instanceof BeanAopBase || !beanInstance.__dispose__)
142
+ continue;
143
+ try {
105
144
  this.runWithInstanceScopeOrAppContext(() => {
106
145
  return beanInstance.__dispose__();
107
146
  });
108
- this.app.meta.module._monkeyModuleSync(
109
- false,
110
- 'beanDisposed',
111
- undefined,
112
- this,
113
- beanInstance,
114
- );
147
+ } catch (err) {
148
+ error ??= err as Error;
115
149
  }
116
150
  }
151
+ } finally {
152
+ for (const prop in beanInstances) {
153
+ if (ownedBeans.has(cast(beanInstances[prop]))) {
154
+ delete beanInstances[prop];
155
+ }
156
+ }
157
+ this[SymbolGetBeanSelectorInnerPromises] = {};
117
158
  }
118
- this[SymbolBeanContainerInstances] = shallowReactive({});
119
- this[SymbolBeanContainerParent] = undefined;
120
- this[SymbolGetBeanSelectorInnerPromises] = {};
159
+ // eslint-disable no-throw-literal
160
+ if (error) throw error;
121
161
  }
122
162
 
123
163
  get containerType(): ContainerType {
@@ -355,6 +395,7 @@ export class BeanContainer {
355
395
  fullName,
356
396
  markReactive,
357
397
  withSelector,
398
+ undefined,
358
399
  ...args,
359
400
  );
360
401
  }
@@ -382,7 +423,16 @@ export class BeanContainer {
382
423
  markReactive?: boolean,
383
424
  ...args
384
425
  ): Promise<T> {
385
- return await this._newBeanInner(false, null, null, beanFullName, markReactive, false, ...args);
426
+ return await this._newBeanInner(
427
+ false,
428
+ null,
429
+ null,
430
+ beanFullName,
431
+ markReactive,
432
+ false,
433
+ undefined,
434
+ ...args,
435
+ );
386
436
  }
387
437
 
388
438
  async _newBeanSelector<T>(
@@ -415,6 +465,7 @@ export class BeanContainer {
415
465
  beanFullName: Constructable<T> | string | undefined,
416
466
  markReactive?: boolean,
417
467
  withSelector?: boolean,
468
+ onBeanRecorded?: (beanInstance: T) => void,
418
469
  ...args
419
470
  ): Promise<T> {
420
471
  // bean options
@@ -432,6 +483,7 @@ export class BeanContainer {
432
483
  false,
433
484
  markReactive,
434
485
  withSelector,
486
+ onBeanRecorded,
435
487
  );
436
488
  }
437
489
  // throw new Error(`bean not found: ${beanFullName}`);
@@ -449,6 +501,7 @@ export class BeanContainer {
449
501
  // default is true: same as inject prop
450
502
  markReactive ?? beanOptions.markReactive ?? true,
451
503
  withSelector,
504
+ onBeanRecorded,
452
505
  );
453
506
  }
454
507
 
@@ -504,6 +557,7 @@ export class BeanContainer {
504
557
  aop: boolean | undefined,
505
558
  markReactive: boolean | undefined,
506
559
  withSelector?: boolean,
560
+ onBeanRecorded?: (beanInstance: T) => void,
507
561
  ): Promise<T> {
508
562
  // prepare
509
563
  const beanInstance = await this._prepareBeanInstance(
@@ -530,6 +584,7 @@ export class BeanContainer {
530
584
  if (recordProp) {
531
585
  this.__recordProp(recordProp, fullName, beanInstance, true);
532
586
  }
587
+ onBeanRecorded?.(beanInstance);
533
588
  }
534
589
  // init
535
590
  await this._initBeanInstance(beanFullName, beanInstance, args);
@@ -781,6 +836,7 @@ export class BeanContainer {
781
836
  targetBeanFullName,
782
837
  markReactive,
783
838
  selectorInfo.withSelector,
839
+ undefined,
784
840
  ...selectorInfo.args,
785
841
  );
786
842
  }
@@ -1,5 +1,6 @@
1
1
  import type { ComponentPublicInstance } from 'vue';
2
2
 
3
+ import type { ZovaContext } from '../../../core/context/context.ts';
3
4
  import type { IErrorObject } from './errorObject.ts';
4
5
 
5
6
  export const SymbolErrorInstanceInfo = Symbol('SymbolErrorInstanceInfo');
@@ -9,10 +10,18 @@ export interface IErrorInstanceInfo {
9
10
  info?: string;
10
11
  }
11
12
 
13
+ export interface IErrorHandlerLoad {
14
+ readonly kind: 'controller-load';
15
+ readonly ctx: ZovaContext;
16
+ readonly originalError: unknown;
17
+ disposition: 'fallback' | 'handled';
18
+ }
19
+
12
20
  export interface IErrorHandlerEventData {
13
21
  err: Error;
14
22
  instance?: ComponentPublicInstance | null;
15
23
  info?: string;
24
+ load?: IErrorHandlerLoad;
16
25
  }
17
26
 
18
27
  export type IErrorHandlerEventResult = Error | undefined;
@@ -85,8 +85,26 @@ export interface DefineModelOptions<T = any, G = T, S = T> {
85
85
  set?: (v: S) => any;
86
86
  }
87
87
 
88
+ export type TypeComponentBoundaryRenderMode = 'block' | 'inline';
89
+ export type TypeComponentBoundaryRetry = () => Promise<void>;
90
+
91
+ export interface IComponentBoundaryLoadingOptions {
92
+ delay?: number;
93
+ }
94
+
95
+ export interface IComponentBoundaryOptions {
96
+ loading?: IComponentBoundaryLoadingOptions;
97
+ renderMode?: TypeComponentBoundaryRenderMode;
98
+ /**
99
+ * Declares that this Controller can safely construct a fresh load graph after a fallback error.
100
+ * It is disabled by default because Controller initialization may include non-idempotent work.
101
+ */
102
+ retry?: boolean;
103
+ }
104
+
88
105
  export type IComponentOptions = Parameters<typeof defineOptions>[0] & {
89
106
  deepExtendDefault?: boolean;
107
+ boundary?: IComponentBoundaryOptions;
90
108
  };
91
109
 
92
110
  export type IEmit = () => void;
@@ -9,11 +9,12 @@ export function createZovaComponentPage<M, R, S>(
9
9
  controller: Constructable<M>,
10
10
  render?: Constructable<R>,
11
11
  style?: Constructable<S>,
12
+ componentOptions?: IComponentOptions,
12
13
  ) {
13
14
  return defineComponent(() => {
14
15
  useControllerPage(controller, render, style);
15
16
  return () => {};
16
- });
17
+ }, prepareComponentOptions(componentOptions));
17
18
  }
18
19
 
19
20
  export function prepareComponentOptions(componentOptions?: IComponentOptions) {