zova-core 5.1.89 → 5.1.91

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 (40) hide show
  1. package/dist/bean/beanContainer.d.ts.map +1 -1
  2. package/dist/bean/resource/error/type.d.ts +8 -0
  3. package/dist/bean/resource/error/type.d.ts.map +1 -1
  4. package/dist/bean/type.d.ts +15 -0
  5. package/dist/bean/type.d.ts.map +1 -1
  6. package/dist/components/component.d.ts +6 -1
  7. package/dist/components/component.d.ts.map +1 -1
  8. package/dist/composables/useController.d.ts.map +1 -1
  9. package/dist/core/component/error.d.ts +7 -0
  10. package/dist/core/component/error.d.ts.map +1 -1
  11. package/dist/core/context/component.d.ts +2 -0
  12. package/dist/core/context/component.d.ts.map +1 -1
  13. package/dist/core/context/hooks.d.ts +3 -2
  14. package/dist/core/context/hooks.d.ts.map +1 -1
  15. package/dist/core/context/meta.d.ts.map +1 -1
  16. package/dist/core/context/state.d.ts +42 -0
  17. package/dist/core/context/state.d.ts.map +1 -1
  18. package/dist/core/sys/config.d.ts +19 -7
  19. package/dist/core/sys/config.d.ts.map +1 -1
  20. package/dist/index.js +484 -126
  21. package/dist/index.js.map +1 -1
  22. package/dist/types/interface/monkey.d.ts +13 -0
  23. package/dist/types/interface/monkey.d.ts.map +1 -1
  24. package/dist/types/utils/powerPartial.d.ts +1 -1
  25. package/dist/types/utils/powerPartial.d.ts.map +1 -1
  26. package/package.json +2 -2
  27. package/src/bean/beanBase.ts +1 -1
  28. package/src/bean/beanContainer.ts +99 -39
  29. package/src/bean/resource/error/type.ts +9 -0
  30. package/src/bean/type.ts +18 -0
  31. package/src/components/component.ts +2 -1
  32. package/src/composables/useController.ts +158 -25
  33. package/src/core/component/error.ts +62 -5
  34. package/src/core/context/component.ts +37 -2
  35. package/src/core/context/hooks.ts +13 -2
  36. package/src/core/context/meta.ts +1 -0
  37. package/src/core/context/state.ts +161 -0
  38. package/src/core/sys/config.ts +32 -7
  39. package/src/types/interface/monkey.ts +15 -0
  40. package/src/types/utils/powerPartial.ts +1 -1
package/dist/index.js CHANGED
@@ -927,7 +927,7 @@ class BeanBase extends BeanBaseSimple {
927
927
  }
928
928
  $controllerMounted(fn) {
929
929
  return this.ctx.util.instanceScope(() => {
930
- return this.ctx.meta.hooks.onMounted(fn);
930
+ return this.ctx.meta.hooks.onMounted(fn, this);
931
931
  });
932
932
  }
933
933
  $customRef(factory) {
@@ -1187,26 +1187,64 @@ class BeanContainer {
1187
1187
  /** @internal */
1188
1188
  dispose() {
1189
1189
  const beanInstances = this[SymbolBeanContainerInstances];
1190
- for (const prop in beanInstances) {
1191
- if (prop.startsWith('$$')) continue;
1192
- const beanInstance = cast(beanInstances[prop]);
1193
- if (beanInstance && !(beanInstance instanceof BeanAopBase) && beanInstance.__dispose__) {
1194
- if (this.containerType === 'sys') {
1195
- this.sys.meta.module._monkeyModuleSync(false, 'beanDispose', undefined, this, beanInstance);
1196
- beanInstance.__dispose__();
1197
- this.sys.meta.module._monkeyModuleSync(false, 'beanDisposed', undefined, this, beanInstance);
1198
- } else {
1199
- this.app.meta.module._monkeyModuleSync(false, 'beanDispose', undefined, this, beanInstance);
1190
+ let error;
1191
+ try {
1192
+ for (const prop in beanInstances) {
1193
+ if (prop.startsWith('$$')) continue;
1194
+ const beanInstance = cast(beanInstances[prop]);
1195
+ if (!beanInstance || beanInstance instanceof BeanAopBase || !beanInstance.__dispose__) continue;
1196
+ try {
1197
+ if (this.containerType === 'sys') {
1198
+ this.sys.meta.module._monkeyModuleSync(false, 'beanDispose', undefined, this, beanInstance);
1199
+ beanInstance.__dispose__();
1200
+ this.sys.meta.module._monkeyModuleSync(false, 'beanDisposed', undefined, this, beanInstance);
1201
+ } else {
1202
+ this.app.meta.module._monkeyModuleSync(false, 'beanDispose', undefined, this, beanInstance);
1203
+ this.runWithInstanceScopeOrAppContext(() => {
1204
+ return beanInstance.__dispose__();
1205
+ });
1206
+ this.app.meta.module._monkeyModuleSync(false, 'beanDisposed', undefined, this, beanInstance);
1207
+ }
1208
+ } catch (err) {
1209
+ error ??= err;
1210
+ }
1211
+ }
1212
+ } finally {
1213
+ this[SymbolBeanContainerInstances] = shallowReactive({});
1214
+ this[SymbolBeanContainerParent] = undefined;
1215
+ this[SymbolGetBeanSelectorInnerPromises] = {};
1216
+ }
1217
+ // eslint-disable no-throw-literal
1218
+ if (error) throw error;
1219
+ }
1220
+
1221
+ /** @internal */
1222
+ retryReset(ownedBeans) {
1223
+ const beanInstances = this[SymbolBeanContainerInstances];
1224
+ let error;
1225
+ try {
1226
+ for (const prop in beanInstances) {
1227
+ const beanInstance = cast(beanInstances[prop]);
1228
+ if (!ownedBeans.has(beanInstance)) continue;
1229
+ if (!beanInstance || beanInstance instanceof BeanAopBase || !beanInstance.__dispose__) continue;
1230
+ try {
1200
1231
  this.runWithInstanceScopeOrAppContext(() => {
1201
1232
  return beanInstance.__dispose__();
1202
1233
  });
1203
- this.app.meta.module._monkeyModuleSync(false, 'beanDisposed', undefined, this, beanInstance);
1234
+ } catch (err) {
1235
+ error ??= err;
1236
+ }
1237
+ }
1238
+ } finally {
1239
+ for (const prop in beanInstances) {
1240
+ if (ownedBeans.has(cast(beanInstances[prop]))) {
1241
+ delete beanInstances[prop];
1204
1242
  }
1205
1243
  }
1244
+ this[SymbolGetBeanSelectorInnerPromises] = {};
1206
1245
  }
1207
- this[SymbolBeanContainerInstances] = shallowReactive({});
1208
- this[SymbolBeanContainerParent] = undefined;
1209
- this[SymbolGetBeanSelectorInnerPromises] = {};
1246
+ // eslint-disable no-throw-literal
1247
+ if (error) throw error;
1210
1248
  }
1211
1249
  get containerType() {
1212
1250
  if (!this.ctx) return 'sys';
@@ -1348,7 +1386,7 @@ class BeanContainer {
1348
1386
  return this[SymbolBeanContainerInstances][key];
1349
1387
  }
1350
1388
  async _getBeanSelectorInnerPromise(recordProp, fullName, markReactive, withSelector, ...args) {
1351
- return await this._newBeanInner(true, recordProp, null, fullName, markReactive, withSelector, ...args);
1389
+ return await this._newBeanInner(true, recordProp, null, fullName, markReactive, withSelector, undefined, ...args);
1352
1390
  }
1353
1391
  _newBeanSimple(A, markReactive, ...args) {
1354
1392
  // prepare
@@ -1362,7 +1400,7 @@ class BeanContainer {
1362
1400
  }
1363
1401
  // async _newBean<T>(beanFullName: string, markReactive?: boolean, ...args): Promise<T>;
1364
1402
  async _newBean(beanFullName, markReactive, ...args) {
1365
- return await this._newBeanInner(false, null, null, beanFullName, markReactive, false, ...args);
1403
+ return await this._newBeanInner(false, null, null, beanFullName, markReactive, false, undefined, ...args);
1366
1404
  }
1367
1405
  // async _newBeanSelector<T>(beanFullName: string, markReactive?: boolean, selector?: string, ...args): Promise<T>;
1368
1406
  async _newBeanSelector(beanFullName, markReactive, selector, ...args) {
@@ -1370,21 +1408,25 @@ class BeanContainer {
1370
1408
  }
1371
1409
 
1372
1410
  /** @internal */
1373
- async _newBeanInner(record, recordProp, controllerData, beanFullName, markReactive, withSelector, ...args) {
1411
+ async _newBeanInner(record, recordProp, controllerData, beanFullName, markReactive, withSelector, onBeanRecorded, ...args) {
1374
1412
  // bean options
1375
1413
  const beanOptions = await this._getBeanOptionsForce(beanFullName);
1376
1414
  if (!beanOptions) {
1377
1415
  // class
1378
1416
  if (typeof beanFullName === 'function' && isClass(beanFullName)) {
1379
- return await this._createBeanInstance(record, recordProp, controllerData, undefined, beanFullName, args, false, markReactive, withSelector);
1417
+ return await this._createBeanInstance(record, recordProp, controllerData, undefined, beanFullName, args, false, markReactive, withSelector, onBeanRecorded);
1380
1418
  }
1381
1419
  // throw new Error(`bean not found: ${beanFullName}`);
1382
1420
  return null;
1383
1421
  }
1422
+ // controller module: ensure route resources are ready before controller data initialization
1423
+ if (controllerData) {
1424
+ await this._useModule(beanOptions.module);
1425
+ }
1384
1426
  // beanFullName
1385
1427
  return await this._createBeanInstance(record, recordProp, controllerData, beanOptions.beanFullName, beanOptions.beanClass, args, cast(beanOptions.scene) === 'aop',
1386
1428
  // default is true: same as inject prop
1387
- markReactive ?? beanOptions.markReactive ?? true, withSelector);
1429
+ markReactive ?? beanOptions.markReactive ?? true, withSelector, onBeanRecorded);
1388
1430
  }
1389
1431
  _getBeanFullNameByComposableOrClassSync(beanFullName) {
1390
1432
  // bean options
@@ -1424,7 +1466,7 @@ class BeanContainer {
1424
1466
  // get
1425
1467
  return appResource.getBean(beanFullName);
1426
1468
  }
1427
- async _createBeanInstance(record, recordProp, controllerData, beanFullName, beanClass, args, aop, markReactive, withSelector) {
1469
+ async _createBeanInstance(record, recordProp, controllerData, beanFullName, beanClass, args, aop, markReactive, withSelector, onBeanRecorded) {
1428
1470
  // prepare
1429
1471
  const beanInstance = await this._prepareBeanInstance(beanFullName, beanClass, args, markReactive, aop);
1430
1472
  // special for controller
@@ -1444,6 +1486,7 @@ class BeanContainer {
1444
1486
  if (recordProp) {
1445
1487
  this.__recordProp(recordProp, fullName, beanInstance, true);
1446
1488
  }
1489
+ onBeanRecorded?.(beanInstance);
1447
1490
  }
1448
1491
  // init
1449
1492
  await this._initBeanInstance(beanFullName, beanInstance, args);
@@ -1640,7 +1683,7 @@ class BeanContainer {
1640
1683
  targetInstance = await this._getBeanSelectorInner(true, recordProp, targetBeanFullName, markReactive, selectorInfo.withSelector, ...selectorInfo.args);
1641
1684
  } else if (injectionScope === 'new') {
1642
1685
  // not record prop
1643
- targetInstance = await this._newBeanInner(false, null, null, targetBeanFullName, markReactive, selectorInfo.withSelector, ...selectorInfo.args);
1686
+ targetInstance = await this._newBeanInner(false, null, null, targetBeanFullName, markReactive, selectorInfo.withSelector, undefined, ...selectorInfo.args);
1644
1687
  }
1645
1688
  return targetInstance;
1646
1689
  }
@@ -2448,7 +2491,8 @@ function configDefault(env) {
2448
2491
  flavor: env.META_FLAVOR,
2449
2492
  mode: env.META_MODE,
2450
2493
  appMode: env.META_APP_MODE
2451
- }
2494
+ },
2495
+ boundary: {}
2452
2496
  };
2453
2497
  return config;
2454
2498
  }
@@ -3476,6 +3520,112 @@ const ClientOnly = defineComponent({
3476
3520
  }
3477
3521
  });
3478
3522
 
3523
+ class AppError extends ErrorClass {
3524
+ /** @internal */
3525
+ async initialize() {
3526
+ await super.initialize();
3527
+ // errorHandler
3528
+ this.app.vue.config.errorHandler = (err, instance, info) => {
3529
+ return this._handleError(err, instance, info);
3530
+ };
3531
+ // unhandledrejection
3532
+ if (process.env.CLIENT) {
3533
+ window.addEventListener('unhandledrejection', event => {
3534
+ event.preventDefault();
3535
+ this._handleUnhandledError(event.reason, 'unhandledrejection');
3536
+ return false;
3537
+ });
3538
+ window.addEventListener('error', event => {
3539
+ event.preventDefault();
3540
+ this._handleUnhandledError(event.error, 'unhandlederror');
3541
+ return false;
3542
+ });
3543
+ }
3544
+ }
3545
+
3546
+ /** @internal */
3547
+ createScopeError(moduleScope, errorCode) {
3548
+ const self = this;
3549
+ return {
3550
+ throw: (...args) => {
3551
+ return self.throw(moduleScope, errorCode, ...args);
3552
+ },
3553
+ parseFail: (...args) => {
3554
+ return self.parseFail(moduleScope, errorCode, ...args);
3555
+ }
3556
+ };
3557
+ }
3558
+ handleLoadError(originalError, ctx, instance) {
3559
+ const err = normalizeError(originalError);
3560
+ const load = {
3561
+ kind: 'controller-load',
3562
+ ctx,
3563
+ originalError,
3564
+ disposition: 'fallback'
3565
+ };
3566
+ this._handleError(err, instance, 'useController:load', load);
3567
+ return {
3568
+ err,
3569
+ disposition: load.disposition
3570
+ };
3571
+ }
3572
+ _handleUnhandledError(error, infoDefault) {
3573
+ if (error instanceof Error) {
3574
+ const errorInfo = error[SymbolErrorInstanceInfo];
3575
+ if (errorInfo) {
3576
+ delete error[SymbolErrorInstanceInfo];
3577
+ }
3578
+ // should not catch error
3579
+ this.app.vue.config.errorHandler(error, errorInfo?.instance, errorInfo?.info || infoDefault);
3580
+ }
3581
+ }
3582
+ _handleError(err, instance, info, load) {
3583
+ if (!this.app) {
3584
+ // means destroyed
3585
+ console.error(err);
3586
+ return;
3587
+ }
3588
+ const err2 = this.app.meta.event.emitSync('app:errorHandler', {
3589
+ err: err,
3590
+ instance,
3591
+ info,
3592
+ load
3593
+ }, data => {
3594
+ return data.err;
3595
+ });
3596
+ // only log error in client
3597
+ if (process.env.CLIENT) {
3598
+ if (!err2 || !(err2 instanceof Error)) return err2;
3599
+ console.error(err2);
3600
+ }
3601
+ return err2;
3602
+ }
3603
+ }
3604
+ function normalizeError(error) {
3605
+ if (error instanceof Error) return error;
3606
+ const normalized = new Error(getErrorMessage(error));
3607
+ if (error && (typeof error === 'object' || typeof error === 'function')) {
3608
+ for (const key of ['code', 'status', 'pagePath', 'url']) {
3609
+ const descriptor = Object.getOwnPropertyDescriptor(error, key);
3610
+ if (!descriptor || !('value' in descriptor)) continue;
3611
+ const value = descriptor.value;
3612
+ if ((key === 'code' || key === 'status') && (typeof value === 'number' || typeof value === 'string')) {
3613
+ normalized[key] = value;
3614
+ }
3615
+ if ((key === 'pagePath' || key === 'url') && typeof value === 'string') {
3616
+ normalized[key] = value;
3617
+ }
3618
+ }
3619
+ }
3620
+ return normalized;
3621
+ }
3622
+ function getErrorMessage(error) {
3623
+ if (error === null || error === undefined || typeof error === 'string' || typeof error === 'number' || typeof error === 'boolean' || typeof error === 'bigint' || typeof error === 'symbol') {
3624
+ return String(error);
3625
+ }
3626
+ return 'Unknown error';
3627
+ }
3628
+
3479
3629
  const SymbolTypeSSRRenderOriginal = Symbol('SymbolTypeSSRRenderOriginal');
3480
3630
  const SymbolTypeSSRRenderResetCount = Symbol('SymbolTypeSSRRenderResetCount');
3481
3631
  class CtxComponent extends BeanSimple {
@@ -3494,8 +3644,12 @@ class CtxComponent extends BeanSimple {
3494
3644
  const self = this;
3495
3645
  instance[renderMethod] = function (...args) {
3496
3646
  if (instance.isUnmounted) return;
3497
- if (!self.ctx.meta.state.inited.state) {
3498
- return self._bean_render_original.call(this, ...args);
3647
+ const loadStatus = self.ctx.meta.state.loadStatus;
3648
+ if (loadStatus === 'loading') {
3649
+ return self.ctx.meta.state.loadLoadingVisible ? self._renderLoading() : null;
3650
+ }
3651
+ if (loadStatus === 'error') {
3652
+ return self._renderError(self.ctx.meta.state.loadError);
3499
3653
  }
3500
3654
  const render = self._getRender();
3501
3655
  if (!render) {
@@ -3549,6 +3703,28 @@ class CtxComponent extends BeanSimple {
3549
3703
  this._renderPatched = false;
3550
3704
  this._ssrRenderReset = false;
3551
3705
  }
3706
+ _renderLoading() {
3707
+ const renderMode = this.ctx.meta.state.loadRenderMode;
3708
+ const controller = this.ctx.meta.state.loadController;
3709
+ if (controller?.renderLoading) {
3710
+ return controller.renderLoading(renderMode);
3711
+ }
3712
+ const renderLoading = this.sys.config.boundary.renderLoading;
3713
+ return renderLoading?.(this.ctx, renderMode) ?? null;
3714
+ }
3715
+ _renderError(error) {
3716
+ const renderMode = this.ctx.meta.state.loadRenderMode;
3717
+ const retry = this.ctx.meta.state.loadRetry;
3718
+ const controller = this.ctx.meta.state.loadController;
3719
+ if (controller?.renderError) {
3720
+ return controller.renderError(error, renderMode, retry);
3721
+ }
3722
+ const renderError = this.sys.config.boundary.renderError;
3723
+ if (renderError) {
3724
+ return renderError(this.ctx, error, renderMode, retry);
3725
+ }
3726
+ return _renderErrorDefault(error);
3727
+ }
3552
3728
  _getRender() {
3553
3729
  const render = this.bean._getBeanSyncOnly(BeanControllerIdentifier);
3554
3730
  if (!render) return;
@@ -3557,6 +3733,12 @@ class CtxComponent extends BeanSimple {
3557
3733
  return this.bean._getBeanSyncOnly(BeanRenderIdentifier);
3558
3734
  }
3559
3735
  }
3736
+ function _renderErrorDefault(error) {
3737
+ if (error instanceof Error) {
3738
+ return error.message;
3739
+ }
3740
+ return String(error);
3741
+ }
3560
3742
 
3561
3743
  const SymbolHooksFns = Symbol('SymbolHooksFns');
3562
3744
  const SymbolHooksState = Symbol('SymbolHooksState');
@@ -3573,9 +3755,19 @@ class CtxHooks extends BeanSimple {
3573
3755
  onCreated(fn) {
3574
3756
  this._onHook('created', fn);
3575
3757
  }
3576
- onMounted(fn) {
3758
+ onMounted(fn, owner) {
3759
+ if (owner && !this.ctx.meta.state.isLoadBeanActive(owner)) return;
3577
3760
  this._onHook('mounted', fn);
3578
3761
  }
3762
+
3763
+ /** @internal */
3764
+ onHydrated(fn) {
3765
+ this._onHook('hydrated', fn);
3766
+ }
3767
+ clearMounted() {
3768
+ if (this[SymbolHooksState].mounted) return;
3769
+ this[SymbolHooksFns].mounted = undefined;
3770
+ }
3579
3771
  _onHook(type, fn) {
3580
3772
  if (this[SymbolHooksState][type]) {
3581
3773
  this.ctx.util.instanceScope(fn);
@@ -3605,17 +3797,142 @@ class CtxState extends BeanSimple {
3605
3797
  super(...args);
3606
3798
  this._id = void 0;
3607
3799
  this._inited = void 0;
3800
+ this._loadStatus = 'loading';
3801
+ this._loadRenderMode = 'block';
3802
+ this._loadRetryEnabled = false;
3803
+ this._loadRetryReady = false;
3804
+ this._loadRetry = void 0;
3805
+ this._loadAttemptId = 0;
3806
+ this._loadBeanAttempts = new WeakMap();
3807
+ this._loadError = void 0;
3808
+ this._loadController = void 0;
3809
+ /** @internal SSR hydration protocol key. */
3810
+ this.controllerLoadKey = void 0;
3811
+ this._loadLoadingVisible = false;
3812
+ this._loadLoadingTimer = void 0;
3608
3813
  }
3609
3814
  __init__() {
3610
3815
  this._id = ++__id;
3611
3816
  this._inited = StateLock.create();
3612
3817
  }
3818
+
3819
+ /** @internal */
3820
+ dispose() {
3821
+ this._clearLoadLoadingTimer();
3822
+ }
3613
3823
  get id() {
3614
3824
  return this._id;
3615
3825
  }
3616
3826
  get inited() {
3617
3827
  return this._inited;
3618
3828
  }
3829
+ get loadStatus() {
3830
+ return this._loadStatus;
3831
+ }
3832
+ get loadRenderMode() {
3833
+ return this._loadRenderMode;
3834
+ }
3835
+ get loadRetry() {
3836
+ if (!this.isLoadRetryAvailable) return undefined;
3837
+ return this._loadRetry;
3838
+ }
3839
+ get isLoadRetryAvailable() {
3840
+ return this._loadRetryEnabled && this._loadRetryReady && this._loadStatus === 'error' && !this.ctx.disposed && !!this._loadRetry;
3841
+ }
3842
+ get loadAttemptId() {
3843
+ return this._loadAttemptId;
3844
+ }
3845
+ get loadError() {
3846
+ return this._loadError;
3847
+ }
3848
+ get loadController() {
3849
+ return this._loadController;
3850
+ }
3851
+ get loadLoadingVisible() {
3852
+ return this._loadLoadingVisible;
3853
+ }
3854
+ setLoadController(controller) {
3855
+ this._loadController = controller;
3856
+ }
3857
+ setLoadRenderMode(renderMode) {
3858
+ this._loadRenderMode = renderMode === 'inline' ? 'inline' : 'block';
3859
+ }
3860
+ setLoadRetryEnabled(retryEnabled) {
3861
+ this._loadRetryEnabled = retryEnabled === true;
3862
+ }
3863
+ setLoadRetry(retry) {
3864
+ this._loadRetry = retry;
3865
+ }
3866
+ setLoadRetryReady() {
3867
+ this._loadRetryReady = true;
3868
+ }
3869
+ beginLoadAttempt(delay) {
3870
+ this._clearLoadLoadingTimer();
3871
+ this._loadAttemptId++;
3872
+ this._loadStatus = 'loading';
3873
+ this._loadError = undefined;
3874
+ this._loadController = undefined;
3875
+ this._loadLoadingVisible = false;
3876
+ if (delay !== undefined) {
3877
+ this.startLoadLoading(delay);
3878
+ }
3879
+ return this._loadAttemptId;
3880
+ }
3881
+ isLoadAttemptActive(attemptId) {
3882
+ return !this.ctx.disposed && this._loadAttemptId === attemptId;
3883
+ }
3884
+ setLoadBeanAttempt(bean, attemptId) {
3885
+ this._loadBeanAttempts.set(bean, attemptId);
3886
+ }
3887
+ getLoadBeanAttempt(bean) {
3888
+ return this._loadBeanAttempts.get(bean);
3889
+ }
3890
+ isLoadBeanActive(bean) {
3891
+ const attemptId = this._loadBeanAttempts.get(bean);
3892
+ return attemptId === undefined || this.isLoadAttemptActive(attemptId);
3893
+ }
3894
+ startLoadLoading(delay) {
3895
+ this._clearLoadLoadingTimer();
3896
+ if (delay === 0) {
3897
+ this._loadLoadingVisible = true;
3898
+ return;
3899
+ }
3900
+ this._loadLoadingTimer = setTimeout(() => {
3901
+ this._loadLoadingTimer = undefined;
3902
+ if (this.ctx.disposed || this._loadStatus !== 'loading') return;
3903
+ this._loadLoadingVisible = true;
3904
+ }, delay);
3905
+ }
3906
+
3907
+ /**
3908
+ * Resolves the Controller-load terminal lock consumed by the custom SSR hydration runtime.
3909
+ * A settled load is not necessarily a successful load: error and handled control-flow paths
3910
+ * must also allow the rendered boundary subtree to hydrate.
3911
+ */
3912
+ settleLoad() {
3913
+ this._inited.touch();
3914
+ }
3915
+ setLoadReady() {
3916
+ this._clearLoadLoadingTimer();
3917
+ this._loadError = undefined;
3918
+ this._loadStatus = 'ready';
3919
+ this.settleLoad();
3920
+ }
3921
+ setLoadError(error) {
3922
+ this._clearLoadLoadingTimer();
3923
+ this._loadError = error;
3924
+ this._loadStatus = 'error';
3925
+ this.settleLoad();
3926
+ }
3927
+ setLoadHandled() {
3928
+ this._clearLoadLoadingTimer();
3929
+ this.settleLoad();
3930
+ }
3931
+ _clearLoadLoadingTimer() {
3932
+ if (this._loadLoadingTimer === undefined) return;
3933
+ clearTimeout(this._loadLoadingTimer);
3934
+ this._loadLoadingTimer = undefined;
3935
+ }
3619
3936
  }
3620
3937
 
3621
3938
  class CtxMeta extends BeanSimple {
@@ -3638,6 +3955,7 @@ class CtxMeta extends BeanSimple {
3638
3955
 
3639
3956
  /** @internal */
3640
3957
  dispose() {
3958
+ this.state.dispose();
3641
3959
  this.component.dispose();
3642
3960
  this.hooks.dispose();
3643
3961
  }
@@ -3758,6 +4076,7 @@ function useController(controllerBeanFullName, renderBeanFullName, styleBeanFull
3758
4076
  async function _useController(controllerData, controllerBeanFullName, renderBeanFullName, styleBeanFullName) {
3759
4077
  // ctx
3760
4078
  const ctx = new ZovaContext(getCurrentInstance());
4079
+ setLoadBoundaryOptions(controllerBeanFullName);
3761
4080
  // ctx: monkey
3762
4081
  if (ctx.app) {
3763
4082
  ctx.app.meta.module._monkeyModuleSync(true, 'appContextInitialize', undefined, ctx);
@@ -3768,54 +4087,162 @@ async function _useController(controllerData, controllerBeanFullName, renderBean
3768
4087
  if (ctx.app) {
3769
4088
  ctx.app.meta.module._monkeyModuleSync(true, 'controllerDataPrepare', undefined, controllerData, ctx);
3770
4089
  }
3771
- if (process.env.CLIENT) {
3772
- // dispose
3773
- onBeforeUnmount(() => {
3774
- if (ctx.disposed) return;
3775
- // undefined better than null
3776
- setControllerRef(ctx, false);
3777
- if (ctx.bean !== ctx.app.bean) {
3778
- ctx.bean.dispose();
3779
- }
3780
- });
3781
- onUnmounted(() => {
3782
- ctx.dispose();
3783
- });
4090
+ let retryPromise;
4091
+ let loadBeans = new Set();
4092
+ function setLoadBoundaryOptions(controllerBeanFullName) {
4093
+ const boundary = controllerBeanFullName.$componentOptions?.boundary;
4094
+ ctx.meta.state.setLoadRenderMode(boundary?.renderMode);
4095
+ ctx.meta.state.setLoadRetryEnabled(boundary?.retry);
3784
4096
  }
3785
- async function __load() {
4097
+ function isAttemptActive(attemptId) {
4098
+ if (ctx.disposed || !ctx.meta) return false;
4099
+ return ctx.meta.state.isLoadAttemptActive(attemptId);
4100
+ }
4101
+ function getLoadLoadingDelay() {
4102
+ const delay = controllerBeanFullName.$componentOptions?.boundary?.loading?.delay ?? sys.config.boundary.loading?.delay;
4103
+ const delayNumber = Number(delay);
4104
+ return Number.isFinite(delayNumber) && delayNumber > 0 ? delayNumber : 0;
4105
+ }
4106
+ async function __loadBeans(attemptId) {
3786
4107
  // controller
3787
- if (ctx.disposed) return;
3788
- await ctx.bean._newBeanInner(true, BeanControllerIdentifier, controllerData, controllerBeanFullName, true, false);
4108
+ if (!isAttemptActive(attemptId)) return false;
4109
+ await ctx.bean._newBeanInner(true, BeanControllerIdentifier, controllerData, controllerBeanFullName, true, false, controller => {
4110
+ if (!isAttemptActive(attemptId)) return;
4111
+ loadBeans.add(controller);
4112
+ ctx.meta.state.setLoadBeanAttempt(controller, attemptId);
4113
+ ctx.meta.state.setLoadController(controller);
4114
+ setLoadBoundaryOptions(controller.constructor);
4115
+ });
4116
+ if (!isAttemptActive(attemptId)) return false;
4117
+ // style
3789
4118
  if (styleBeanFullName) {
3790
- if (ctx.disposed) return;
3791
- await ctx.bean._newBeanInner(true, BeanStyleIdentifier, undefined, styleBeanFullName, true, false);
4119
+ await ctx.bean._newBeanInner(true, BeanStyleIdentifier, undefined, styleBeanFullName, true, false, style => {
4120
+ if (!isAttemptActive(attemptId)) return;
4121
+ loadBeans.add(style);
4122
+ ctx.meta.state.setLoadBeanAttempt(style, attemptId);
4123
+ });
3792
4124
  }
4125
+ if (!isAttemptActive(attemptId)) return false;
4126
+ // render
3793
4127
  if (renderBeanFullName) {
3794
- if (ctx.disposed) return;
3795
- await ctx.bean._newBeanInner(true, BeanRenderIdentifier, undefined, renderBeanFullName, true, false);
4128
+ await ctx.bean._newBeanInner(true, BeanRenderIdentifier, undefined, renderBeanFullName, true, false, render => {
4129
+ if (!isAttemptActive(attemptId)) return;
4130
+ loadBeans.add(render);
4131
+ ctx.meta.state.setLoadBeanAttempt(render, attemptId);
4132
+ });
3796
4133
  }
3797
- // must touch inited on server/client, force router.use effect
3798
- if (ctx.disposed) return;
3799
- ctx.meta.state.inited.touch();
4134
+ return isAttemptActive(attemptId);
4135
+ }
4136
+ async function __load(attemptId) {
4137
+ if (!(await __loadBeans(attemptId))) return;
4138
+ if (!isAttemptActive(attemptId)) return;
4139
+ ctx.meta.state.setLoadReady();
3800
4140
  if (process.env.CLIENT) {
3801
4141
  ctx.util.instanceScope(() => {
3802
4142
  queuePostFlushCb(() => {
4143
+ if (!isAttemptActive(attemptId)) return;
3803
4144
  setControllerRef(ctx, true);
3804
4145
  ctx.meta.hooks.invokeHook('mounted');
3805
4146
  });
3806
4147
  });
3807
4148
  }
3808
4149
  }
4150
+ async function runLoadAttempt(attemptId, beforeLoad) {
4151
+ try {
4152
+ await beforeLoad?.();
4153
+ if (!isAttemptActive(attemptId)) return;
4154
+ await __load(attemptId);
4155
+ } catch (err) {
4156
+ if (!isAttemptActive(attemptId)) return;
4157
+ const result = ctx.app?.meta.error.handleLoadError(err, ctx, ctx.instance) ?? {
4158
+ err: normalizeError(err),
4159
+ disposition: 'fallback'
4160
+ };
4161
+ if (!isAttemptActive(attemptId)) return;
4162
+ if (result.disposition === 'handled') {
4163
+ ctx.meta.state.setLoadHandled();
4164
+ return;
4165
+ }
4166
+ ctx.app?.meta.module._monkeyModuleSync(true, 'controllerLoad', undefined, {
4167
+ phase: 'fallback',
4168
+ ctx,
4169
+ controllerBeanFullName,
4170
+ controllerBeanName: typeof controllerBeanFullName === 'string' ? controllerBeanFullName : controllerBeanFullName.name,
4171
+ error: result.err,
4172
+ controllerRecorded: !!ctx.meta.state.loadController
4173
+ });
4174
+ if (!isAttemptActive(attemptId)) return;
4175
+ ctx.meta.state.setLoadError(result.err);
4176
+ }
4177
+ }
4178
+ function retry() {
4179
+ if (!ctx.meta.state.isLoadRetryAvailable) return Promise.resolve();
4180
+ if (retryPromise) return retryPromise;
4181
+ const promise = (async () => {
4182
+ const attemptId = ctx.meta.state.beginLoadAttempt(getLoadLoadingDelay());
4183
+ const previousLoadBeans = loadBeans;
4184
+ loadBeans = new Set();
4185
+ ctx.meta.hooks.clearMounted();
4186
+ setControllerRef(ctx, false);
4187
+ await runLoadAttempt(attemptId, () => {
4188
+ ctx.bean.retryReset(previousLoadBeans);
4189
+ });
4190
+ })();
4191
+ retryPromise = promise;
4192
+ void promise.finally(() => {
4193
+ if (retryPromise === promise) {
4194
+ retryPromise = undefined;
4195
+ }
4196
+ });
4197
+ return promise;
4198
+ }
4199
+ ctx.meta.state.setLoadRetry(retry);
4200
+ if (process.env.CLIENT) {
4201
+ ctx.meta.hooks.onHydrated(() => {
4202
+ if (!ctx.disposed) {
4203
+ ctx.meta.state.setLoadRetryReady();
4204
+ }
4205
+ });
4206
+ // dispose
4207
+ onBeforeUnmount(() => {
4208
+ if (ctx.disposed) return;
4209
+ // undefined better than null
4210
+ setControllerRef(ctx, false);
4211
+ if (ctx.bean !== ctx.app.bean) {
4212
+ ctx.bean.dispose();
4213
+ }
4214
+ });
4215
+ onUnmounted(() => {
4216
+ ctx.dispose();
4217
+ });
4218
+ }
3809
4219
 
3810
4220
  // load
3811
4221
  ctx.meta.hooks.onCreated(async () => {
3812
4222
  if (ctx.disposed) return;
3813
- try {
3814
- return await __load();
3815
- } catch (err) {
3816
- if (ctx.disposed) return;
3817
- throw err;
4223
+ const loadEvent = {
4224
+ phase: 'prepare',
4225
+ ctx,
4226
+ controllerBeanFullName,
4227
+ controllerBeanName: typeof controllerBeanFullName === 'string' ? controllerBeanFullName : controllerBeanFullName.name
4228
+ };
4229
+ ctx.app?.meta.module._monkeyModuleSync(true, 'controllerLoad', undefined, loadEvent);
4230
+ if (loadEvent.replayError) {
4231
+ const attemptId = ctx.meta.state.beginLoadAttempt();
4232
+ if (loadEvent.replayControllerRecorded) {
4233
+ try {
4234
+ await __loadBeans(attemptId);
4235
+ } catch {
4236
+ // The server's fallback decision is authoritative during initial hydration.
4237
+ }
4238
+ }
4239
+ if (!ctx.disposed) {
4240
+ ctx.meta.state.setLoadError(loadEvent.replayError);
4241
+ }
4242
+ return;
3818
4243
  }
4244
+ const attemptId = ctx.meta.state.beginLoadAttempt(getLoadLoadingDelay());
4245
+ await runLoadAttempt(attemptId);
3819
4246
  });
3820
4247
  if (process.env.SERVER) {
3821
4248
  onServerPrefetch(() => {
@@ -3836,11 +4263,11 @@ function setControllerRef(ctx, on) {
3836
4263
  }
3837
4264
  }
3838
4265
 
3839
- function createZovaComponentPage(controller, render, style) {
4266
+ function createZovaComponentPage(controller, render, style, componentOptions) {
3840
4267
  return defineComponent(() => {
3841
4268
  useControllerPage(controller, render, style);
3842
4269
  return () => {};
3843
- });
4270
+ }, prepareComponentOptions(componentOptions));
3844
4271
  }
3845
4272
  function prepareComponentOptions(componentOptions) {
3846
4273
  return Object.assign({
@@ -3882,75 +4309,6 @@ class AppComponent extends BeanSimple {
3882
4309
  }
3883
4310
  }
3884
4311
 
3885
- class AppError extends ErrorClass {
3886
- /** @internal */
3887
- async initialize() {
3888
- await super.initialize();
3889
- // errorHandler
3890
- this.app.vue.config.errorHandler = (err, instance, info) => {
3891
- return this._handleError(err, instance, info);
3892
- };
3893
- // unhandledrejection
3894
- if (process.env.CLIENT) {
3895
- window.addEventListener('unhandledrejection', event => {
3896
- event.preventDefault();
3897
- this._handleUnhandledError(event.reason, 'unhandledrejection');
3898
- return false;
3899
- });
3900
- window.addEventListener('error', event => {
3901
- event.preventDefault();
3902
- this._handleUnhandledError(event.error, 'unhandlederror');
3903
- return false;
3904
- });
3905
- }
3906
- }
3907
-
3908
- /** @internal */
3909
- createScopeError(moduleScope, errorCode) {
3910
- const self = this;
3911
- return {
3912
- throw: (...args) => {
3913
- return self.throw(moduleScope, errorCode, ...args);
3914
- },
3915
- parseFail: (...args) => {
3916
- return self.parseFail(moduleScope, errorCode, ...args);
3917
- }
3918
- };
3919
- }
3920
- _handleUnhandledError(error, infoDefault) {
3921
- if (error instanceof Error) {
3922
- const errorInfo = error[SymbolErrorInstanceInfo];
3923
- if (errorInfo) {
3924
- delete error[SymbolErrorInstanceInfo];
3925
- }
3926
- // should not catch error
3927
- this.app.vue.config.errorHandler(error, errorInfo?.instance, errorInfo?.info || infoDefault);
3928
- }
3929
- }
3930
- _handleError(err, instance, info) {
3931
- if (!this.app) {
3932
- // means destroyed
3933
- console.error(err);
3934
- return;
3935
- }
3936
- const err2 = this.app.meta.event.emitSync('app:errorHandler', {
3937
- err: err,
3938
- instance,
3939
- info
3940
- }, data => {
3941
- return data.err;
3942
- });
3943
- // only log error in client
3944
- if (process.env.CLIENT) {
3945
- if (!err2 || !(err2 instanceof Error)) return err2;
3946
- if (!info || !['useMutationData'].includes(info)) {
3947
- console.error(err2);
3948
- }
3949
- }
3950
- return err2;
3951
- }
3952
- }
3953
-
3954
4312
  class AppModule extends BeanSimple {
3955
4313
  constructor(...args) {
3956
4314
  super(...args);
@@ -4437,5 +4795,5 @@ function $protocolKey(key) {
4437
4795
  return key;
4438
4796
  }
4439
4797
 
4440
- export { $getBeanName, $protocolKey, AppError, AppLocale, AppMeta, AppMetadata, AppModule, AppResource, BeanAopBase, BeanAopMethodBase, BeanBase, BeanBaseSimple, BeanContainer, BeanControllerBase, BeanControllerIdentifier, BeanControllerLike, BeanControllerPageBase, BeanInfo, BeanRenderBase, BeanRenderIdentifier, BeanRenderLike, BeanScopeBase, BeanScopeError, BeanScopeLocale, BeanScopeUtil, BeanSimple, BeanStyleBase, BeanStyleIdentifier, ClientOnly, CtxComponent, CtxMeta, CtxState, CtxUtil, DecoratorBeanFullName, ErrorClass, LocaleModuleNameSeparator, LocalePattern, MixinClass, OmitClass, PartialClass, PickClass, PluginBean, PluginZova, Preload, ProxyDisable, StateLock, SymbolBeanContainerInstances, SymbolBeanFullName, SymbolBeanInstanceKey, SymbolController, SymbolControllerRefDisable, SymbolDecoratorBeanInfo, SymbolDecoratorPreload, SymbolDecoratorProxyDisable, SymbolDecoratorUse, SymbolDecoratorVirtual, SymbolDecoratorVueElements, SymbolErrorInstanceInfo, SymbolInited, SymbolInstalled, SymbolMappedClassMetadataKeys, SymbolModuleBelong, SymbolModuleName, SysComponent, SysError, SysLocale, SysLogger, SysMeta, SysModule, SysUtil, Use, UseScope, Virtual, ZovaApplication, ZovaContext, ZovaSys, __prepareInjectSelectorInfo, appMetadata, appResource, beanFullNameFromOnionName, bootstrap, cast, configDefault, constantDefault, convertToUnit, copyMetadataOfClasses, copyProperties, copyPropertiesOfClasses, createBeanDecorator, createVueDecorator, createZovaComponentAsync, createZovaComponentPage, deepEqual, deepExtend, disposeInstance, errorsInternal, formatLoggerConsole, formatLoggerFilter, functionNoop, getLoggerFilterLevel, getMappedClassMetadataKeys, isHttpUrl, isUuid, localeDefault, modelGet, modelSet, objectAssignReactive, onionNameFromBeanFullName, polyfillDispose, prepareComponentOptions, registerMappedClassMetadataKey, setLoggerFilterLevel, ssr_load_local, sys, throwErrorComponentUnmounted, useApp, useComputed, useContext, useController, useControllerPage, usePrepareArg, usePrepareArgs, uuid };
4798
+ export { $getBeanName, $protocolKey, AppError, AppLocale, AppMeta, AppMetadata, AppModule, AppResource, BeanAopBase, BeanAopMethodBase, BeanBase, BeanBaseSimple, BeanContainer, BeanControllerBase, BeanControllerIdentifier, BeanControllerLike, BeanControllerPageBase, BeanInfo, BeanRenderBase, BeanRenderIdentifier, BeanRenderLike, BeanScopeBase, BeanScopeError, BeanScopeLocale, BeanScopeUtil, BeanSimple, BeanStyleBase, BeanStyleIdentifier, ClientOnly, CtxComponent, CtxMeta, CtxState, CtxUtil, DecoratorBeanFullName, ErrorClass, LocaleModuleNameSeparator, LocalePattern, MixinClass, OmitClass, PartialClass, PickClass, PluginBean, PluginZova, Preload, ProxyDisable, StateLock, SymbolBeanContainerInstances, SymbolBeanFullName, SymbolBeanInstanceKey, SymbolController, SymbolControllerRefDisable, SymbolDecoratorBeanInfo, SymbolDecoratorPreload, SymbolDecoratorProxyDisable, SymbolDecoratorUse, SymbolDecoratorVirtual, SymbolDecoratorVueElements, SymbolErrorInstanceInfo, SymbolInited, SymbolInstalled, SymbolMappedClassMetadataKeys, SymbolModuleBelong, SymbolModuleName, SysComponent, SysError, SysLocale, SysLogger, SysMeta, SysModule, SysUtil, Use, UseScope, Virtual, ZovaApplication, ZovaContext, ZovaSys, __prepareInjectSelectorInfo, appMetadata, appResource, beanFullNameFromOnionName, bootstrap, cast, configDefault, constantDefault, convertToUnit, copyMetadataOfClasses, copyProperties, copyPropertiesOfClasses, createBeanDecorator, createVueDecorator, createZovaComponentAsync, createZovaComponentPage, deepEqual, deepExtend, disposeInstance, errorsInternal, formatLoggerConsole, formatLoggerFilter, functionNoop, getLoggerFilterLevel, getMappedClassMetadataKeys, isHttpUrl, isUuid, localeDefault, modelGet, modelSet, normalizeError, objectAssignReactive, onionNameFromBeanFullName, polyfillDispose, prepareComponentOptions, registerMappedClassMetadataKey, setLoggerFilterLevel, ssr_load_local, sys, throwErrorComponentUnmounted, useApp, useComputed, useContext, useController, useControllerPage, usePrepareArg, usePrepareArgs, uuid };
4441
4799
  //# sourceMappingURL=index.js.map