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.
- package/.cabloy-version +1 -1
- package/CHANGELOG.md +12 -0
- package/package.json +2 -2
- package/repo-docs/frontend/component-guide.md +33 -0
- package/repo-docs/frontend/model-resource-cookbook.md +0 -8
- package/repo-docs/frontend/model-resource-usage-guide.md +0 -4
- package/repo-e2e/specs/cabloy-basic.spec.ts +158 -0
- package/repo-e2e/specs/controller-boundary.spec.ts +261 -0
- package/test-results/.last-run.json +1 -1
- package/vona/packages-utils/lint/package.json +1 -1
- package/vona/packages-utils/lint/src/oxc/lint.ts +1 -1
- package/vona/packages-utils/lint/src/oxc/lintVue.ts +1 -1
- package/vona/pnpm-lock.yaml +238 -46
- package/vona/pnpm-workspace.yaml +1 -1
- package/zova/package.original.json +1 -1
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/package.json +3 -3
- package/zova/packages-utils/babel-plugin-zova-behavior/package.json +2 -1
- package/zova/packages-utils/babel-plugin-zova-behavior/src/index.ts +21 -8
- package/zova/packages-utils/babel-plugin-zova-behavior/test/behaviorSlots.test.ts +111 -0
- package/zova/packages-utils/zova-jsx/package.json +2 -2
- package/zova/packages-utils/zova-vite/package.json +2 -2
- package/zova/packages-zova/zova/package.json +3 -3
- package/zova/packages-zova/zova-core/package.json +1 -1
- package/zova/packages-zova/zova-core/src/bean/beanBase.ts +1 -1
- package/zova/packages-zova/zova-core/src/bean/beanContainer.ts +95 -39
- package/zova/packages-zova/zova-core/src/bean/resource/error/type.ts +9 -0
- package/zova/packages-zova/zova-core/src/bean/type.ts +18 -0
- package/zova/packages-zova/zova-core/src/components/component.ts +2 -1
- package/zova/packages-zova/zova-core/src/composables/useController.ts +155 -25
- package/zova/packages-zova/zova-core/src/core/component/error.ts +62 -5
- package/zova/packages-zova/zova-core/src/core/context/component.ts +37 -2
- package/zova/packages-zova/zova-core/src/core/context/hooks.ts +13 -2
- package/zova/packages-zova/zova-core/src/core/context/meta.ts +1 -0
- package/zova/packages-zova/zova-core/src/core/context/state.ts +161 -0
- package/zova/packages-zova/zova-core/src/core/sys/config.ts +32 -7
- package/zova/packages-zova/zova-core/src/types/interface/monkey.ts +15 -0
- package/zova/packages-zova/zova-core/src/types/utils/powerPartial.ts +1 -1
- package/zova/pnpm-lock.yaml +40 -25
- package/zova/src/front/config/config/config.ts +11 -0
- package/zova/src/front/config/config/render.tsx +79 -0
- package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +0 -1
- package/zova/src/suite/a-demo/modules/demo-basic/src/.metadata/component/controllerBoundaryProbe.ts +31 -0
- package/zova/src/suite/a-demo/modules/demo-basic/src/.metadata/component/controllerBoundaryProbeInline.ts +34 -0
- package/zova/src/suite/a-demo/modules/demo-basic/src/.metadata/component/controllerBoundaryProbeOverride.ts +34 -0
- package/zova/src/suite/a-demo/modules/demo-basic/src/.metadata/index.ts +58 -1
- package/zova/src/suite/a-demo/modules/demo-basic/src/.metadata/page/controllerBoundary.ts +23 -0
- package/zova/src/suite/a-demo/modules/demo-basic/src/component/controllerBoundaryProbe/controller.tsx +36 -0
- package/zova/src/suite/a-demo/modules/demo-basic/src/component/controllerBoundaryProbeInline/controller.tsx +34 -0
- package/zova/src/suite/a-demo/modules/demo-basic/src/component/controllerBoundaryProbeOverride/controller.tsx +46 -0
- package/zova/src/suite/a-demo/modules/demo-basic/src/page/controllerBoundary/controller.tsx +185 -0
- package/zova/src/suite/a-demo/modules/demo-basic/src/routes.ts +6 -0
- package/zova/src/suite/a-home/modules/home-base/src/config/locale/en-us.ts +1 -0
- package/zova/src/suite/a-home/modules/home-base/src/config/locale/zh-cn.ts +1 -0
- package/zova/src/suite/a-training/modules/training-student/src/model/student.ts +0 -3
- package/zova/src/suite/cabloy-basic/modules/basic-button/package.json +58 -0
- package/zova/src/suite/cabloy-basic/modules/basic-button/src/.metadata/component/button.ts +31 -0
- package/zova/src/suite/cabloy-basic/modules/basic-button/src/.metadata/index.ts +122 -0
- package/zova/src/suite/cabloy-basic/modules/basic-button/src/.metadata/this.ts +2 -0
- package/zova/src/suite/cabloy-basic/modules/basic-button/src/bean/behavior.perform.tsx +103 -0
- package/zova/src/suite/cabloy-basic/modules/basic-button/src/component/button/controller.tsx +39 -0
- package/zova/src/suite/cabloy-basic/modules/basic-button/src/index.ts +1 -0
- package/zova/src/suite/cabloy-basic/modules/basic-button/tsconfig.build.json +13 -0
- package/zova/src/suite/cabloy-basic/modules/basic-button/tsconfig.json +5 -0
- package/zova/src/suite/cabloy-basic/modules/basic-details/package.json +2 -1
- package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/tableCell.actionDelete.tsx +4 -4
- package/zova/src/suite/cabloy-basic/modules/basic-page/src/component/blockPage/controller.tsx +5 -1
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx +5 -1
- package/zova/src/suite/cabloy-basic/modules/basic-table/package.json +2 -1
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/bean/tableCell.actionDelete.tsx +4 -4
- package/zova/src/suite/cabloy-basic/modules/basic-table/src/component/actionRefresh/controller.tsx +10 -8
- package/zova/src/suite/cabloy-basic/package.json +1 -0
- package/zova/src/suite-vendor/a-cabloy/modules/a-layoutprofile/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/a-layoutprofile/src/model/layoutprofile.ts +0 -3
- 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/.metadata/page/resource.ts +6 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/model/resource.ts +0 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/resource/controller.tsx +5 -1
- package/zova/src/suite-vendor/a-cabloy/package.json +3 -3
- package/zova/src/suite-vendor/a-zova/modules/a-bean/cli/controller/metadata/generateFilePage.ts +3 -1
- package/zova/src/suite-vendor/a-zova/modules/a-bean/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useMutation.ts +1 -11
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useQuery.ts +1 -17
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/utils.ts +2 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/types/query.ts +2 -7
- package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/mutation.types.ts +14 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/queryEnsureLoaded.test.ts +56 -0
- package/zova/src/suite-vendor/a-zova/modules/a-router/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/monkey.ts +8 -0
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssr.ts +85 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/monkey.ts +25 -1
- package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/types/ssr.ts +15 -1
- package/zova/src/suite-vendor/a-zova/modules/a-zova/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/package.json +6 -6
- /package/vona/patches/{zova-core@5.1.89.patch → zova-core@5.1.90.patch} +0 -0
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
|
|
10
10
|
import type { IBeanRecord, IControllerData } from '../bean/type.ts';
|
|
11
11
|
import type { Constructable } from '../decorator/index.ts';
|
|
12
|
+
import type { IControllerLoadEvent } from '../types/interface/monkey.ts';
|
|
12
13
|
|
|
13
14
|
import {
|
|
14
15
|
BeanControllerIdentifier,
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
BeanStyleIdentifier,
|
|
17
18
|
SymbolControllerRefDisable,
|
|
18
19
|
} from '../bean/type.ts';
|
|
20
|
+
import { normalizeError } from '../core/component/error.ts';
|
|
19
21
|
import { ZovaContext } from '../core/context/index.ts';
|
|
20
22
|
import { sys } from '../core/sys/sys.ts';
|
|
21
23
|
|
|
@@ -81,6 +83,7 @@ async function _useController(
|
|
|
81
83
|
) {
|
|
82
84
|
// ctx
|
|
83
85
|
const ctx = new ZovaContext(getCurrentInstance()!);
|
|
86
|
+
setLoadBoundaryOptions(controllerBeanFullName);
|
|
84
87
|
// ctx: monkey
|
|
85
88
|
if (ctx.app) {
|
|
86
89
|
ctx.app.meta.module._monkeyModuleSync(true, 'appContextInitialize', undefined, ctx);
|
|
@@ -97,24 +100,31 @@ async function _useController(
|
|
|
97
100
|
ctx,
|
|
98
101
|
);
|
|
99
102
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
onUnmounted(() => {
|
|
111
|
-
ctx.dispose();
|
|
112
|
-
});
|
|
103
|
+
|
|
104
|
+
let retryPromise: Promise<void> | undefined;
|
|
105
|
+
let loadBeans = new Set<object>();
|
|
106
|
+
|
|
107
|
+
function setLoadBoundaryOptions(controllerBeanFullName: Constructable | string) {
|
|
108
|
+
const boundary = (controllerBeanFullName as any).$componentOptions?.boundary;
|
|
109
|
+
ctx.meta.state.setLoadRenderMode(boundary?.renderMode);
|
|
110
|
+
ctx.meta.state.setLoadRetryEnabled(boundary?.retry);
|
|
113
111
|
}
|
|
114
112
|
|
|
115
|
-
|
|
113
|
+
function isAttemptActive(attemptId: number) {
|
|
114
|
+
return ctx.meta.state.isLoadAttemptActive(attemptId);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getLoadLoadingDelay() {
|
|
118
|
+
const delay =
|
|
119
|
+
(controllerBeanFullName as any).$componentOptions?.boundary?.loading?.delay ??
|
|
120
|
+
sys.config.boundary.loading?.delay;
|
|
121
|
+
const delayNumber = Number(delay);
|
|
122
|
+
return Number.isFinite(delayNumber) && delayNumber > 0 ? delayNumber : 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function __loadBeans(attemptId: number) {
|
|
116
126
|
// controller
|
|
117
|
-
if (
|
|
127
|
+
if (!isAttemptActive(attemptId)) return false;
|
|
118
128
|
await ctx.bean._newBeanInner(
|
|
119
129
|
true,
|
|
120
130
|
BeanControllerIdentifier,
|
|
@@ -122,9 +132,17 @@ async function _useController(
|
|
|
122
132
|
controllerBeanFullName,
|
|
123
133
|
true,
|
|
124
134
|
false,
|
|
135
|
+
controller => {
|
|
136
|
+
loadBeans.add(controller as object);
|
|
137
|
+
ctx.meta.state.setLoadBeanAttempt(controller as object, attemptId);
|
|
138
|
+
if (!isAttemptActive(attemptId)) return;
|
|
139
|
+
ctx.meta.state.setLoadController(controller);
|
|
140
|
+
setLoadBoundaryOptions((controller as any).constructor);
|
|
141
|
+
},
|
|
125
142
|
);
|
|
143
|
+
if (!isAttemptActive(attemptId)) return false;
|
|
144
|
+
// style
|
|
126
145
|
if (styleBeanFullName) {
|
|
127
|
-
if (ctx.disposed) return;
|
|
128
146
|
await ctx.bean._newBeanInner(
|
|
129
147
|
true,
|
|
130
148
|
BeanStyleIdentifier,
|
|
@@ -132,10 +150,15 @@ async function _useController(
|
|
|
132
150
|
styleBeanFullName,
|
|
133
151
|
true,
|
|
134
152
|
false,
|
|
153
|
+
style => {
|
|
154
|
+
loadBeans.add(style as object);
|
|
155
|
+
ctx.meta.state.setLoadBeanAttempt(style as object, attemptId);
|
|
156
|
+
},
|
|
135
157
|
);
|
|
136
158
|
}
|
|
159
|
+
if (!isAttemptActive(attemptId)) return false;
|
|
160
|
+
// render
|
|
137
161
|
if (renderBeanFullName) {
|
|
138
|
-
if (ctx.disposed) return;
|
|
139
162
|
await ctx.bean._newBeanInner(
|
|
140
163
|
true,
|
|
141
164
|
BeanRenderIdentifier,
|
|
@@ -143,14 +166,23 @@ async function _useController(
|
|
|
143
166
|
renderBeanFullName,
|
|
144
167
|
true,
|
|
145
168
|
false,
|
|
169
|
+
render => {
|
|
170
|
+
loadBeans.add(render as object);
|
|
171
|
+
ctx.meta.state.setLoadBeanAttempt(render as object, attemptId);
|
|
172
|
+
},
|
|
146
173
|
);
|
|
147
174
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
175
|
+
return isAttemptActive(attemptId);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function __load(attemptId: number) {
|
|
179
|
+
if (!(await __loadBeans(attemptId))) return;
|
|
180
|
+
if (!isAttemptActive(attemptId)) return;
|
|
181
|
+
ctx.meta.state.setLoadReady();
|
|
151
182
|
if (process.env.CLIENT) {
|
|
152
183
|
ctx.util.instanceScope(() => {
|
|
153
184
|
queuePostFlushCb(() => {
|
|
185
|
+
if (!isAttemptActive(attemptId)) return;
|
|
154
186
|
setControllerRef(ctx, true);
|
|
155
187
|
ctx.meta.hooks.invokeHook('mounted');
|
|
156
188
|
});
|
|
@@ -158,15 +190,113 @@ async function _useController(
|
|
|
158
190
|
}
|
|
159
191
|
}
|
|
160
192
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
193
|
+
async function runLoadAttempt(
|
|
194
|
+
attemptId: number,
|
|
195
|
+
beforeLoad?: () => void | Promise<void>,
|
|
196
|
+
): Promise<void> {
|
|
164
197
|
try {
|
|
165
|
-
|
|
198
|
+
await beforeLoad?.();
|
|
199
|
+
if (!isAttemptActive(attemptId)) return;
|
|
200
|
+
await __load(attemptId);
|
|
166
201
|
} catch (err) {
|
|
202
|
+
if (!isAttemptActive(attemptId)) return;
|
|
203
|
+
const result = ctx.app?.meta.error.handleLoadError(err, ctx, ctx.instance as any) ?? {
|
|
204
|
+
err: normalizeError(err),
|
|
205
|
+
disposition: 'fallback' as const,
|
|
206
|
+
};
|
|
207
|
+
if (!isAttemptActive(attemptId)) return;
|
|
208
|
+
if (result.disposition === 'handled') {
|
|
209
|
+
ctx.meta.state.setLoadHandled();
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
ctx.app?.meta.module._monkeyModuleSync(true, 'controllerLoad', undefined, {
|
|
213
|
+
phase: 'fallback',
|
|
214
|
+
ctx,
|
|
215
|
+
controllerBeanFullName,
|
|
216
|
+
controllerBeanName:
|
|
217
|
+
typeof controllerBeanFullName === 'string'
|
|
218
|
+
? controllerBeanFullName
|
|
219
|
+
: controllerBeanFullName.name,
|
|
220
|
+
error: result.err,
|
|
221
|
+
controllerRecorded: !!ctx.meta.state.loadController,
|
|
222
|
+
});
|
|
223
|
+
if (!isAttemptActive(attemptId)) return;
|
|
224
|
+
ctx.meta.state.setLoadError(result.err);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function retry() {
|
|
229
|
+
if (!ctx.meta.state.isLoadRetryAvailable) return Promise.resolve();
|
|
230
|
+
if (retryPromise) return retryPromise;
|
|
231
|
+
const promise = (async () => {
|
|
232
|
+
const attemptId = ctx.meta.state.beginLoadAttempt(getLoadLoadingDelay());
|
|
233
|
+
const previousLoadBeans = loadBeans;
|
|
234
|
+
loadBeans = new Set();
|
|
235
|
+
ctx.meta.hooks.clearMounted();
|
|
236
|
+
setControllerRef(ctx, false);
|
|
237
|
+
await runLoadAttempt(attemptId, () => {
|
|
238
|
+
ctx.bean.retryReset(previousLoadBeans);
|
|
239
|
+
});
|
|
240
|
+
})();
|
|
241
|
+
retryPromise = promise;
|
|
242
|
+
void promise.finally(() => {
|
|
243
|
+
if (retryPromise === promise) {
|
|
244
|
+
retryPromise = undefined;
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
return promise;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
ctx.meta.state.setLoadRetry(retry);
|
|
251
|
+
if (process.env.CLIENT) {
|
|
252
|
+
ctx.meta.hooks.onHydrated(() => {
|
|
253
|
+
if (!ctx.disposed) {
|
|
254
|
+
ctx.meta.state.setLoadRetryReady();
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
// dispose
|
|
258
|
+
onBeforeUnmount(() => {
|
|
167
259
|
if (ctx.disposed) return;
|
|
168
|
-
|
|
260
|
+
// undefined better than null
|
|
261
|
+
setControllerRef(ctx, false);
|
|
262
|
+
if (ctx.bean !== ctx.app.bean) {
|
|
263
|
+
ctx.bean.dispose();
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
onUnmounted(() => {
|
|
267
|
+
ctx.dispose();
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// load
|
|
272
|
+
ctx.meta.hooks.onCreated(async () => {
|
|
273
|
+
if (ctx.disposed) return;
|
|
274
|
+
const loadEvent: IControllerLoadEvent = {
|
|
275
|
+
phase: 'prepare',
|
|
276
|
+
ctx,
|
|
277
|
+
controllerBeanFullName,
|
|
278
|
+
controllerBeanName:
|
|
279
|
+
typeof controllerBeanFullName === 'string'
|
|
280
|
+
? controllerBeanFullName
|
|
281
|
+
: controllerBeanFullName.name,
|
|
282
|
+
};
|
|
283
|
+
ctx.app?.meta.module._monkeyModuleSync(true, 'controllerLoad', undefined, loadEvent);
|
|
284
|
+
if (loadEvent.replayError) {
|
|
285
|
+
const attemptId = ctx.meta.state.beginLoadAttempt();
|
|
286
|
+
if (loadEvent.replayControllerRecorded) {
|
|
287
|
+
try {
|
|
288
|
+
await __loadBeans(attemptId);
|
|
289
|
+
} catch {
|
|
290
|
+
// The server's fallback decision is authoritative during initial hydration.
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
if (!ctx.disposed) {
|
|
294
|
+
ctx.meta.state.setLoadError(loadEvent.replayError);
|
|
295
|
+
}
|
|
296
|
+
return;
|
|
169
297
|
}
|
|
298
|
+
const attemptId = ctx.meta.state.beginLoadAttempt(getLoadLoadingDelay());
|
|
299
|
+
await runLoadAttempt(attemptId);
|
|
170
300
|
});
|
|
171
301
|
if (process.env.SERVER) {
|
|
172
302
|
onServerPrefetch(() => {
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import type { ComponentPublicInstance } from 'vue';
|
|
2
2
|
|
|
3
3
|
import type { IErrorObject } from '../../bean/resource/error/errorObject.ts';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
IErrorHandlerLoad,
|
|
6
|
+
IErrorInstanceInfo,
|
|
7
|
+
IModuleError,
|
|
8
|
+
} from '../../bean/resource/error/type.ts';
|
|
9
|
+
import type { ZovaContext } from '../context/context.ts';
|
|
5
10
|
|
|
6
11
|
import { ErrorClass } from '../../bean/resource/error/errorClass.ts';
|
|
7
12
|
import { SymbolErrorInstanceInfo } from '../../bean/resource/error/type.ts';
|
|
@@ -42,6 +47,22 @@ export class AppError extends ErrorClass {
|
|
|
42
47
|
};
|
|
43
48
|
}
|
|
44
49
|
|
|
50
|
+
public handleLoadError(
|
|
51
|
+
originalError: unknown,
|
|
52
|
+
ctx: ZovaContext,
|
|
53
|
+
instance?: ComponentPublicInstance | null,
|
|
54
|
+
) {
|
|
55
|
+
const err = normalizeError(originalError);
|
|
56
|
+
const load: IErrorHandlerLoad = {
|
|
57
|
+
kind: 'controller-load',
|
|
58
|
+
ctx,
|
|
59
|
+
originalError,
|
|
60
|
+
disposition: 'fallback',
|
|
61
|
+
};
|
|
62
|
+
this._handleError(err, instance, 'useController:load', load);
|
|
63
|
+
return { err, disposition: load.disposition };
|
|
64
|
+
}
|
|
65
|
+
|
|
45
66
|
private _handleUnhandledError(error: Error, infoDefault: string) {
|
|
46
67
|
if (error instanceof Error) {
|
|
47
68
|
const errorInfo: IErrorInstanceInfo | undefined = error[SymbolErrorInstanceInfo];
|
|
@@ -61,6 +82,7 @@ export class AppError extends ErrorClass {
|
|
|
61
82
|
err: Error,
|
|
62
83
|
instance: ComponentPublicInstance | null | undefined,
|
|
63
84
|
info: string | undefined,
|
|
85
|
+
load?: IErrorHandlerLoad,
|
|
64
86
|
) {
|
|
65
87
|
if (!this.app) {
|
|
66
88
|
// means destroyed
|
|
@@ -69,7 +91,7 @@ export class AppError extends ErrorClass {
|
|
|
69
91
|
}
|
|
70
92
|
const err2 = this.app.meta.event.emitSync(
|
|
71
93
|
'app:errorHandler',
|
|
72
|
-
{ err: err as Error, instance, info },
|
|
94
|
+
{ err: err as Error, instance, info, load },
|
|
73
95
|
data => {
|
|
74
96
|
return data.err;
|
|
75
97
|
},
|
|
@@ -77,10 +99,45 @@ export class AppError extends ErrorClass {
|
|
|
77
99
|
// only log error in client
|
|
78
100
|
if (process.env.CLIENT) {
|
|
79
101
|
if (!err2 || !(err2 instanceof Error)) return err2;
|
|
80
|
-
|
|
81
|
-
console.error(err2);
|
|
82
|
-
}
|
|
102
|
+
console.error(err2);
|
|
83
103
|
}
|
|
84
104
|
return err2;
|
|
85
105
|
}
|
|
86
106
|
}
|
|
107
|
+
|
|
108
|
+
export function normalizeError(error: unknown) {
|
|
109
|
+
if (error instanceof Error) return error;
|
|
110
|
+
const normalized = new Error(getErrorMessage(error));
|
|
111
|
+
if (error && (typeof error === 'object' || typeof error === 'function')) {
|
|
112
|
+
for (const key of ['code', 'status', 'pagePath', 'url']) {
|
|
113
|
+
const descriptor = Object.getOwnPropertyDescriptor(error, key);
|
|
114
|
+
if (!descriptor || !('value' in descriptor)) continue;
|
|
115
|
+
const value = descriptor.value;
|
|
116
|
+
if (
|
|
117
|
+
(key === 'code' || key === 'status') &&
|
|
118
|
+
(typeof value === 'number' || typeof value === 'string')
|
|
119
|
+
) {
|
|
120
|
+
(normalized as any)[key] = value;
|
|
121
|
+
}
|
|
122
|
+
if ((key === 'pagePath' || key === 'url') && typeof value === 'string') {
|
|
123
|
+
(normalized as any)[key] = value;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return normalized;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function getErrorMessage(error: unknown) {
|
|
131
|
+
if (
|
|
132
|
+
error === null ||
|
|
133
|
+
error === undefined ||
|
|
134
|
+
typeof error === 'string' ||
|
|
135
|
+
typeof error === 'number' ||
|
|
136
|
+
typeof error === 'boolean' ||
|
|
137
|
+
typeof error === 'bigint' ||
|
|
138
|
+
typeof error === 'symbol'
|
|
139
|
+
) {
|
|
140
|
+
return String(error);
|
|
141
|
+
}
|
|
142
|
+
return 'Unknown error';
|
|
143
|
+
}
|
|
@@ -21,8 +21,12 @@ export class CtxComponent extends BeanSimple {
|
|
|
21
21
|
const self = this;
|
|
22
22
|
instance[renderMethod] = function (this, ...args) {
|
|
23
23
|
if (instance.isUnmounted) return;
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
const loadStatus = self.ctx.meta.state.loadStatus;
|
|
25
|
+
if (loadStatus === 'loading') {
|
|
26
|
+
return self.ctx.meta.state.loadLoadingVisible ? self._renderLoading() : null;
|
|
27
|
+
}
|
|
28
|
+
if (loadStatus === 'error') {
|
|
29
|
+
return self._renderError(self.ctx.meta.state.loadError);
|
|
26
30
|
}
|
|
27
31
|
const render = self._getRender();
|
|
28
32
|
if (!render) {
|
|
@@ -77,6 +81,30 @@ export class CtxComponent extends BeanSimple {
|
|
|
77
81
|
this._ssrRenderReset = false;
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
private _renderLoading() {
|
|
85
|
+
const renderMode = this.ctx.meta.state.loadRenderMode;
|
|
86
|
+
const controller = this.ctx.meta.state.loadController as any;
|
|
87
|
+
if (controller?.renderLoading) {
|
|
88
|
+
return controller.renderLoading(renderMode);
|
|
89
|
+
}
|
|
90
|
+
const renderLoading = this.sys.config.boundary.renderLoading;
|
|
91
|
+
return renderLoading?.(this.ctx, renderMode) ?? null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private _renderError(error: unknown) {
|
|
95
|
+
const renderMode = this.ctx.meta.state.loadRenderMode;
|
|
96
|
+
const retry = this.ctx.meta.state.loadRetry;
|
|
97
|
+
const controller = this.ctx.meta.state.loadController as any;
|
|
98
|
+
if (controller?.renderError) {
|
|
99
|
+
return controller.renderError(error, renderMode, retry);
|
|
100
|
+
}
|
|
101
|
+
const renderError = this.sys.config.boundary.renderError;
|
|
102
|
+
if (renderError) {
|
|
103
|
+
return renderError(this.ctx, error, renderMode, retry);
|
|
104
|
+
}
|
|
105
|
+
return _renderErrorDefault(error);
|
|
106
|
+
}
|
|
107
|
+
|
|
80
108
|
private _getRender(): any {
|
|
81
109
|
const render = this.bean._getBeanSyncOnly<BeanControllerBase>(BeanControllerIdentifier);
|
|
82
110
|
if (!render) return;
|
|
@@ -85,3 +113,10 @@ export class CtxComponent extends BeanSimple {
|
|
|
85
113
|
return this.bean._getBeanSyncOnly(BeanRenderIdentifier);
|
|
86
114
|
}
|
|
87
115
|
}
|
|
116
|
+
|
|
117
|
+
function _renderErrorDefault(error: unknown) {
|
|
118
|
+
if (error instanceof Error) {
|
|
119
|
+
return error.message;
|
|
120
|
+
}
|
|
121
|
+
return String(error);
|
|
122
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { BeanSimple } from '../../bean/beanSimple.ts';
|
|
2
2
|
|
|
3
|
-
export type ZovaHookType = 'created' | 'mounted';
|
|
3
|
+
export type ZovaHookType = 'created' | 'mounted' | 'hydrated';
|
|
4
4
|
|
|
5
5
|
export const SymbolHooksFns = Symbol('SymbolHooksFns');
|
|
6
6
|
export const SymbolHooksState = Symbol('SymbolHooksState');
|
|
@@ -18,10 +18,21 @@ export class CtxHooks extends BeanSimple {
|
|
|
18
18
|
this._onHook('created', fn);
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
onMounted(fn: any) {
|
|
21
|
+
onMounted(fn: any, owner?: object) {
|
|
22
|
+
if (owner && !this.ctx.meta.state.isLoadBeanActive(owner)) return;
|
|
22
23
|
this._onHook('mounted', fn);
|
|
23
24
|
}
|
|
24
25
|
|
|
26
|
+
/** @internal */
|
|
27
|
+
public onHydrated(fn: any) {
|
|
28
|
+
this._onHook('hydrated', fn);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
clearMounted() {
|
|
32
|
+
if (this[SymbolHooksState].mounted) return;
|
|
33
|
+
this[SymbolHooksFns].mounted = undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
25
36
|
private _onHook(type: ZovaHookType, fn: any) {
|
|
26
37
|
if (this[SymbolHooksState][type]) {
|
|
27
38
|
this.ctx.util.instanceScope(fn);
|
|
@@ -1,17 +1,42 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TypeComponentBoundaryRenderMode,
|
|
3
|
+
TypeComponentBoundaryRetry,
|
|
4
|
+
} from '../../bean/type.ts';
|
|
5
|
+
|
|
1
6
|
import { BeanSimple } from '../../bean/beanSimple.ts';
|
|
2
7
|
import { StateLock } from '../../utils/stateLock.ts';
|
|
3
8
|
|
|
9
|
+
export type CtxLoadStatus = 'loading' | 'ready' | 'error';
|
|
10
|
+
|
|
4
11
|
let __id: number = 0;
|
|
5
12
|
|
|
6
13
|
export class CtxState extends BeanSimple {
|
|
7
14
|
private _id: number;
|
|
8
15
|
private _inited: StateLock;
|
|
16
|
+
private _loadStatus: CtxLoadStatus = 'loading';
|
|
17
|
+
private _loadRenderMode: TypeComponentBoundaryRenderMode = 'block';
|
|
18
|
+
private _loadRetryEnabled = false;
|
|
19
|
+
private _loadRetryReady = false;
|
|
20
|
+
private _loadRetry?: TypeComponentBoundaryRetry;
|
|
21
|
+
private _loadAttemptId = 0;
|
|
22
|
+
private _loadBeanAttempts = new WeakMap<object, number>();
|
|
23
|
+
private _loadError: unknown;
|
|
24
|
+
private _loadController: unknown;
|
|
25
|
+
/** @internal SSR hydration protocol key. */
|
|
26
|
+
public controllerLoadKey?: string;
|
|
27
|
+
private _loadLoadingVisible = false;
|
|
28
|
+
private _loadLoadingTimer: ReturnType<typeof setTimeout> | undefined;
|
|
9
29
|
|
|
10
30
|
protected __init__() {
|
|
11
31
|
this._id = ++__id;
|
|
12
32
|
this._inited = StateLock.create();
|
|
13
33
|
}
|
|
14
34
|
|
|
35
|
+
/** @internal */
|
|
36
|
+
public dispose() {
|
|
37
|
+
this._clearLoadLoadingTimer();
|
|
38
|
+
}
|
|
39
|
+
|
|
15
40
|
get id() {
|
|
16
41
|
return this._id;
|
|
17
42
|
}
|
|
@@ -19,4 +44,140 @@ export class CtxState extends BeanSimple {
|
|
|
19
44
|
get inited() {
|
|
20
45
|
return this._inited;
|
|
21
46
|
}
|
|
47
|
+
|
|
48
|
+
get loadStatus() {
|
|
49
|
+
return this._loadStatus;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
get loadRenderMode() {
|
|
53
|
+
return this._loadRenderMode;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
get loadRetry() {
|
|
57
|
+
if (!this.isLoadRetryAvailable) return undefined;
|
|
58
|
+
return this._loadRetry;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
get isLoadRetryAvailable() {
|
|
62
|
+
return (
|
|
63
|
+
this._loadRetryEnabled &&
|
|
64
|
+
this._loadRetryReady &&
|
|
65
|
+
this._loadStatus === 'error' &&
|
|
66
|
+
!this.ctx.disposed &&
|
|
67
|
+
!!this._loadRetry
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
get loadAttemptId() {
|
|
72
|
+
return this._loadAttemptId;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
get loadError() {
|
|
76
|
+
return this._loadError;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get loadController() {
|
|
80
|
+
return this._loadController;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
get loadLoadingVisible() {
|
|
84
|
+
return this._loadLoadingVisible;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
setLoadController(controller: unknown) {
|
|
88
|
+
this._loadController = controller;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setLoadRenderMode(renderMode: unknown) {
|
|
92
|
+
this._loadRenderMode = renderMode === 'inline' ? 'inline' : 'block';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
setLoadRetryEnabled(retryEnabled: unknown) {
|
|
96
|
+
this._loadRetryEnabled = retryEnabled === true;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
setLoadRetry(retry: TypeComponentBoundaryRetry) {
|
|
100
|
+
this._loadRetry = retry;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
setLoadRetryReady() {
|
|
104
|
+
this._loadRetryReady = true;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
beginLoadAttempt(delay?: number) {
|
|
108
|
+
this._clearLoadLoadingTimer();
|
|
109
|
+
this._loadAttemptId++;
|
|
110
|
+
this._loadStatus = 'loading';
|
|
111
|
+
this._loadError = undefined;
|
|
112
|
+
this._loadController = undefined;
|
|
113
|
+
this._loadLoadingVisible = false;
|
|
114
|
+
if (delay !== undefined) {
|
|
115
|
+
this.startLoadLoading(delay);
|
|
116
|
+
}
|
|
117
|
+
return this._loadAttemptId;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
isLoadAttemptActive(attemptId: number) {
|
|
121
|
+
return !this.ctx.disposed && this._loadAttemptId === attemptId;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
setLoadBeanAttempt(bean: object, attemptId: number) {
|
|
125
|
+
this._loadBeanAttempts.set(bean, attemptId);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
getLoadBeanAttempt(bean: object) {
|
|
129
|
+
return this._loadBeanAttempts.get(bean);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
isLoadBeanActive(bean: object) {
|
|
133
|
+
const attemptId = this._loadBeanAttempts.get(bean);
|
|
134
|
+
return attemptId === undefined || this.isLoadAttemptActive(attemptId);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
startLoadLoading(delay: number) {
|
|
138
|
+
this._clearLoadLoadingTimer();
|
|
139
|
+
if (delay === 0) {
|
|
140
|
+
this._loadLoadingVisible = true;
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
this._loadLoadingTimer = setTimeout(() => {
|
|
144
|
+
this._loadLoadingTimer = undefined;
|
|
145
|
+
if (this.ctx.disposed || this._loadStatus !== 'loading') return;
|
|
146
|
+
this._loadLoadingVisible = true;
|
|
147
|
+
}, delay);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Resolves the Controller-load terminal lock consumed by the custom SSR hydration runtime.
|
|
152
|
+
* A settled load is not necessarily a successful load: error and handled control-flow paths
|
|
153
|
+
* must also allow the rendered boundary subtree to hydrate.
|
|
154
|
+
*/
|
|
155
|
+
settleLoad() {
|
|
156
|
+
this._inited.touch();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
setLoadReady() {
|
|
160
|
+
this._clearLoadLoadingTimer();
|
|
161
|
+
this._loadError = undefined;
|
|
162
|
+
this._loadStatus = 'ready';
|
|
163
|
+
this.settleLoad();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
setLoadError(error: unknown) {
|
|
167
|
+
this._clearLoadLoadingTimer();
|
|
168
|
+
this._loadError = error;
|
|
169
|
+
this._loadStatus = 'error';
|
|
170
|
+
this.settleLoad();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
setLoadHandled() {
|
|
174
|
+
this._clearLoadLoadingTimer();
|
|
175
|
+
this.settleLoad();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private _clearLoadLoadingTimer() {
|
|
179
|
+
if (this._loadLoadingTimer === undefined) return;
|
|
180
|
+
clearTimeout(this._loadLoadingTimer);
|
|
181
|
+
this._loadLoadingTimer = undefined;
|
|
182
|
+
}
|
|
22
183
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ZovaConfigMeta } from '@cabloy/module-info';
|
|
2
2
|
import type ms from 'ms';
|
|
3
|
+
import type { VNodeChild } from 'vue';
|
|
3
4
|
|
|
4
5
|
import type {
|
|
5
6
|
ILayoutRecord,
|
|
@@ -8,9 +9,14 @@ import type {
|
|
|
8
9
|
} from '../../bean/resource/component/type.ts';
|
|
9
10
|
import type { ILocaleRecord } from '../../bean/resource/locale/type.ts';
|
|
10
11
|
import type { ZovaConfigRoutes } from '../../bean/resource/page/type.ts';
|
|
11
|
-
import type {
|
|
12
|
+
import type {
|
|
13
|
+
IBeanScopeConfig,
|
|
14
|
+
TypeComponentBoundaryRenderMode,
|
|
15
|
+
TypeComponentBoundaryRetry,
|
|
16
|
+
} from '../../bean/type.ts';
|
|
12
17
|
import type { ZovaConfigEnv } from '../../types/utils/env.ts';
|
|
13
18
|
import type { PowerPartial } from '../../types/utils/powerPartial.ts';
|
|
19
|
+
import type { ZovaContext } from '../context/context.ts';
|
|
14
20
|
import type { ConfigLogger } from '../logger/types.ts';
|
|
15
21
|
|
|
16
22
|
export function configDefault(env: ZovaConfigEnv): PowerPartial<ZovaConfig> {
|
|
@@ -20,10 +26,33 @@ export function configDefault(env: ZovaConfigEnv): PowerPartial<ZovaConfig> {
|
|
|
20
26
|
mode: env.META_MODE,
|
|
21
27
|
appMode: env.META_APP_MODE,
|
|
22
28
|
},
|
|
29
|
+
boundary: {},
|
|
23
30
|
};
|
|
24
31
|
return config;
|
|
25
32
|
}
|
|
26
33
|
|
|
34
|
+
export interface ZovaConfigApp {
|
|
35
|
+
name: string;
|
|
36
|
+
title: string;
|
|
37
|
+
description: string;
|
|
38
|
+
version: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface ZovaConfigBoundaryLoading {
|
|
42
|
+
delay?: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ZovaConfigBoundary {
|
|
46
|
+
loading?: ZovaConfigBoundaryLoading;
|
|
47
|
+
renderLoading?: (ctx: ZovaContext, renderMode: TypeComponentBoundaryRenderMode) => VNodeChild;
|
|
48
|
+
renderError?: (
|
|
49
|
+
ctx: ZovaContext,
|
|
50
|
+
error: unknown,
|
|
51
|
+
renderMode: TypeComponentBoundaryRenderMode,
|
|
52
|
+
retry?: TypeComponentBoundaryRetry,
|
|
53
|
+
) => VNodeChild;
|
|
54
|
+
}
|
|
55
|
+
|
|
27
56
|
export interface ZovaConfigSsrResponseCachePolicy {
|
|
28
57
|
expires?: number | ms.StringValue;
|
|
29
58
|
}
|
|
@@ -35,12 +64,8 @@ export interface ZovaConfigSsrProfile {
|
|
|
35
64
|
|
|
36
65
|
export interface ZovaConfig {
|
|
37
66
|
meta: ZovaConfigMeta;
|
|
38
|
-
app:
|
|
39
|
-
|
|
40
|
-
title: string;
|
|
41
|
-
description: string;
|
|
42
|
-
version: string;
|
|
43
|
-
};
|
|
67
|
+
app: ZovaConfigApp;
|
|
68
|
+
boundary: ZovaConfigBoundary;
|
|
44
69
|
api: {
|
|
45
70
|
baseURL: string;
|
|
46
71
|
prefix: string;
|