cabloy 5.1.207 → 5.1.209

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +13 -0
  3. package/README.md +1 -0
  4. package/package.json +1 -1
  5. package/repo-agent-governance/managed-assets.json +9 -9
  6. package/repo-agent-governance/policies/repository.md +1 -1
  7. package/repo-agent-governance/skills/cabloy-workflow/SKILL.md +4 -2
  8. package/repo-agent-governance/skills/cabloy-workflow/evals/evals.json +13 -1
  9. package/repo-docs/backend/unit-testing.md +1 -1
  10. package/repo-docs/frontend/form-guide.md +17 -1
  11. package/repo-docs/frontend/model-architecture.md +4 -0
  12. package/repo-docs/frontend/model-state-guide.md +23 -2
  13. package/repo-docs/frontend/resource-entry-page-deep-dive.md +119 -0
  14. package/repo-docs/frontend/scripts.md +3 -3
  15. package/repo-docs/frontend/zova-form-under-the-hood.md +5 -0
  16. package/repo-docs/fullstack/parallel-worktree-environment.md +3 -3
  17. package/repo-docs/fullstack/quickstart.md +11 -1
  18. package/repo-docs/reference/repo-scripts.md +19 -5
  19. package/repo-e2e/specs/cabloy-basic.spec.ts +1577 -0
  20. package/vona/packages-vona/vona/package.json +1 -1
  21. package/vona/src/suite/a-home/modules/home-user/src/controller/passport.ts +1 -0
  22. package/vona/src/suite/a-home/modules/home-user/src/dto/passport.ts +4 -0
  23. package/vona/src/suite/a-home/modules/home-user/test/passport.test.ts +172 -0
  24. package/vona/src/suite/a-home/modules/home-user/test/role.test.ts +1 -0
  25. package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
  26. package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
  27. package/vona/src/suite-vendor/a-vona/modules/a-openapi/package.json +1 -1
  28. package/vona/src/suite-vendor/a-vona/modules/a-openapi/src/types/rest.ts +1 -0
  29. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  30. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  31. package/zova/packages-cli/cli/package.json +2 -2
  32. package/zova/packages-cli/cli-set-front/cli/templates/rest/render.ts +2 -0
  33. package/zova/packages-cli/cli-set-front/cli/templates/rest/rest.ts +9 -0
  34. package/zova/packages-cli/cli-set-front/package.json +1 -1
  35. package/zova/packages-zova/zova/package.json +2 -2
  36. package/zova/src/suite/a-home/modules/home-api/src/.metadata/index.ts +17 -17
  37. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/schemas.ts +68 -52
  38. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/types.ts +524 -343
  39. package/zova/src/suite/a-home/modules/home-passport/src/model/passport.ts +45 -11
  40. package/zova/src/suite/a-home/modules/home-passport/test/lib/persistenceIdentity.test.ts +229 -0
  41. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/package.json +9 -1
  42. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/.metadata/index.ts +81 -5
  43. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/bean/bean.draftCoordinator.ts +158 -0
  44. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockForm/controller.tsx +11 -3
  45. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx +438 -8
  46. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/config/config.ts +11 -0
  47. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/config/locale/en-us.ts +9 -0
  48. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/config/locale/zh-cn.ts +9 -0
  49. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/lib/draftSchema.ts +649 -0
  50. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/model/draft.ts +59 -0
  51. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/monkey.ts +11 -0
  52. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/types/draft.ts +49 -0
  53. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftDate.test.ts +262 -0
  54. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftLifecycle.test.ts +1073 -0
  55. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftLoader.mjs +13 -0
  56. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftModel.test.ts +572 -0
  57. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftSchema.test.ts +427 -0
  58. package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftSchemaSafety.test.ts +200 -0
  59. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
  60. package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/entry/controller.tsx +6 -1
  61. package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
  62. package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
  63. package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/form/controller.tsx +86 -1
  64. package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/formField/controller.tsx +88 -13
  65. package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/schemaRefresh.ts +751 -0
  66. package/zova/src/suite-vendor/a-zova/modules/a-form/test/lib/schemaRefresh.test.ts +637 -0
  67. package/zova/src/suite-vendor/a-zova/modules/a-form/test/lib/schemaRefreshLifecycle.test.ts +994 -0
  68. package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
  69. package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.persister.ts +331 -87
  70. package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.query.ts +11 -6
  71. package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useQuery.ts +3 -1
  72. package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useState.ts +17 -1
  73. package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/index.ts +1 -0
  74. package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/modelPersistence.ts +204 -0
  75. package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/persistenceQueue.ts +50 -0
  76. package/zova/src/suite-vendor/a-zova/modules/a-model/src/monkey.ts +5 -0
  77. package/zova/src/suite-vendor/a-zova/modules/a-model/src/service/storage.ts +47 -2
  78. package/zova/src/suite-vendor/a-zova/modules/a-model/src/types/query.ts +3 -0
  79. package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/modelLoader.mjs +34 -0
  80. package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/modelPersistence.test.ts +727 -0
  81. package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/persistenceQueue.test.ts +137 -0
  82. package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/persistenceTransition.test.ts +129 -0
  83. package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
  84. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/monkey.ts +7 -5
  85. package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.ts +2 -0
  86. package/zova/src/suite-vendor/a-zova/modules/a-router/package.json +1 -1
  87. package/zova/src/suite-vendor/a-zova/modules/a-router/src/bean/bean.router.ts +14 -0
  88. package/zova/src/suite-vendor/a-zova/modules/a-router/src/lib/routeDiscard.ts +26 -0
  89. package/zova/src/suite-vendor/a-zova/modules/a-router/src/service/routerGuards.ts +6 -3
  90. package/zova/src/suite-vendor/a-zova/modules/a-router/src/types/index.ts +1 -0
  91. package/zova/src/suite-vendor/a-zova/modules/a-router/src/types/routeDiscard.ts +24 -0
  92. package/zova/src/suite-vendor/a-zova/modules/a-router/test/lib/routeDiscard.test.ts +105 -0
  93. package/zova/src/suite-vendor/a-zova/modules/a-routertabs/package.json +1 -1
  94. package/zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts +202 -30
  95. package/zova/src/suite-vendor/a-zova/modules/a-routertabs/test/lib/tabs.test.ts +524 -0
  96. package/zova/src/suite-vendor/a-zova/modules/a-routertabs/test/lib/tabsLoader.mjs +35 -0
  97. package/zova/src/suite-vendor/a-zova/package.json +6 -6
@@ -12,7 +12,7 @@ import {
12
12
  } from '@tanstack/vue-form';
13
13
  import { useSelector } from '@tanstack/vue-store';
14
14
  import { SchemaObject } from 'openapi3-ts/oas31';
15
- import { VNode } from 'vue';
15
+ import { markRaw, nextTick, VNode } from 'vue';
16
16
  import { z } from 'zod';
17
17
  import { $ZodIssue } from 'zod/v4/core';
18
18
  import { cast, deepEqual, deepExtend, objectAssignReactive, UseScope } from 'zova';
@@ -31,9 +31,15 @@ import {
31
31
  TypeFormSchemaScene,
32
32
  } from 'zova-module-a-openapi';
33
33
 
34
+ import type { ISchemaRefreshContext } from '../../lib/schemaRefresh.js';
34
35
  import type { IResolvedFormLayoutNode, IResolvedFormLayoutTab } from '../../types/formLayout.js';
35
36
 
36
37
  import { BeanControllerFormBase } from '../../lib/beanControllerFormBase.js';
38
+ import {
39
+ createSchemaRefreshContract,
40
+ schemaRefreshFingerprint,
41
+ SchemaRefreshTracker,
42
+ } from '../../lib/schemaRefresh.js';
37
43
  import {
38
44
  IFormScope,
39
45
  RevalidateLogicProps,
@@ -56,6 +62,7 @@ export interface ControllerFormProps<TFormData extends {} = {}, TSubmitMeta = ne
56
62
  data?: TFormData;
57
63
  schema?: SchemaObject;
58
64
  schemaScene?: TypeFormSchemaScene;
65
+ localePresentationRefresh?: { identity: string };
59
66
  zodSchema?: z.ZodObject<any>;
60
67
  validateOnDynamic?: boolean;
61
68
  validateOnDynamicLogic?: RevalidateLogicProps;
@@ -69,6 +76,7 @@ export interface ControllerFormProps<TFormData extends {} = {}, TSubmitMeta = ne
69
76
  onSubmitData?: TypeFormOnSubmit<TFormData, TSubmitMeta>;
70
77
  onShowError?: TypeFormOnShowError<TFormData, TSubmitMeta>;
71
78
  onChanged?: (data: TFormData) => void;
79
+ onSchemaRefreshed?: (data: TFormData) => void;
72
80
  slotDefault?: (form: ControllerForm<TFormData, TSubmitMeta>) => VNode;
73
81
  slotHeader?: (form: ControllerForm<TFormData, TSubmitMeta>) => VNode;
74
82
  slotBody?: (children: VNode, form: ControllerForm<TFormData, TSubmitMeta>) => VNode;
@@ -91,6 +99,11 @@ export class ControllerForm<
91
99
  properties: ISchemaObjectExtensionField[] | undefined;
92
100
  zovaJsx: ZovaJsx;
93
101
  fieldCelEnv: typeof celEnvBase;
102
+ schemaRefresh: Omit<ISchemaRefreshContext, 'configuration' | 'eligible'>;
103
+ private _schemaRefreshTracker = markRaw(new SchemaRefreshTracker());
104
+ private _schemaRefreshEpoch = 0;
105
+ private _schemaRefreshPending = false;
106
+ private _schemaRefreshDisposed = false;
94
107
 
95
108
  @UseScope()
96
109
  $$scopeOpenapi: ScopeModuleAOpenapi;
@@ -114,6 +127,37 @@ export class ControllerForm<
114
127
  this.properties = this.$computed(() => {
115
128
  return this.$sdk.loadSchemaProperties(this.schema, this.$props.schemaScene);
116
129
  });
130
+ this.schemaRefresh = this.$computed(() => {
131
+ const locale = this.app.meta.locale.current;
132
+ const schema = this.schema;
133
+ const policy = this.$props.localePresentationRefresh;
134
+ const contract = schema
135
+ ? createSchemaRefreshContract(schema, this.properties ?? [], reference => {
136
+ return this.$sdk.getSchema(reference).data;
137
+ })
138
+ : { ready: !policy, fields: {} };
139
+ if (schema && this.$sdk.selector !== locale) contract.ready = false;
140
+ return {
141
+ locale,
142
+ identity: policy?.identity,
143
+ epoch: this._schemaRefreshEpoch,
144
+ scene: schemaRefreshFingerprint([
145
+ this.$props.schemaScene,
146
+ this.formMeta,
147
+ this.formProvider,
148
+ this.$props.zodSchema,
149
+ this.$props.validateOnDynamic,
150
+ this.$props.validateOnDynamicLogic,
151
+ ]),
152
+ enabled: !!policy && !this.$props.zodSchema,
153
+ contract,
154
+ };
155
+ });
156
+ this.$watch(
157
+ () => this.schemaRefresh.contract,
158
+ contract => this._schemaRefreshTracker.prune(contract),
159
+ { flush: 'sync' },
160
+ );
117
161
  this.fieldCelEnv = this._getFieldCelEnv();
118
162
  this.zovaJsx = this.bean._newBeanSimple(
119
163
  ZovaJsx,
@@ -124,6 +168,7 @@ export class ControllerForm<
124
168
  this.$watch(
125
169
  () => this.$props.data,
126
170
  (newValue, oldValue) => {
171
+ ++this._schemaRefreshEpoch;
127
172
  if (deepEqual(newValue, oldValue)) return;
128
173
  this.reset(this.$props.data);
129
174
  },
@@ -138,7 +183,47 @@ export class ControllerForm<
138
183
  return !error && this.formState.isValid;
139
184
  }
140
185
 
186
+ protected __dispose__() {
187
+ this._schemaRefreshDisposed = true;
188
+ ++this._schemaRefreshEpoch;
189
+ this._schemaRefreshTracker.clear();
190
+ }
191
+
192
+ public hasSchemaRefresh(name: string) {
193
+ return this._schemaRefreshTracker.has(name);
194
+ }
195
+
196
+ public consumeSchemaRefresh(name: string, configuration: string, eligible: boolean) {
197
+ return this._schemaRefreshTracker.consume(name, {
198
+ ...this.schemaRefresh,
199
+ configuration,
200
+ eligible,
201
+ });
202
+ }
203
+
204
+ public schemaFieldRefreshed() {
205
+ if (this._schemaRefreshPending || !this.$props.onSchemaRefreshed) return;
206
+ this._schemaRefreshPending = true;
207
+ const epoch = this._schemaRefreshEpoch;
208
+ const locale = this.app.meta.locale.current;
209
+ const { identity, scene } = this.schemaRefresh;
210
+ void nextTick(() => {
211
+ this._schemaRefreshPending = false;
212
+ if (
213
+ !this._schemaRefreshDisposed &&
214
+ epoch === this._schemaRefreshEpoch &&
215
+ locale === this.app.meta.locale.current &&
216
+ identity === this.schemaRefresh.identity &&
217
+ scene === this.schemaRefresh.scene &&
218
+ this.schemaRefresh.contract.ready
219
+ ) {
220
+ this.$props.onSchemaRefreshed?.(this.form.state.values);
221
+ }
222
+ });
223
+ }
224
+
141
225
  public reset(values?: TFormData, opts?: { keepDefaultValues?: boolean }): TFormData {
226
+ ++this._schemaRefreshEpoch;
142
227
  this.form.reset(values ?? ({} as TFormData), opts);
143
228
  return this.form.state.values;
144
229
  }
@@ -3,12 +3,13 @@ import { useField } from '@tanstack/vue-form';
3
3
  import { useSelector } from '@tanstack/vue-store';
4
4
  import { markRaw } from 'vue';
5
5
  import z from 'zod';
6
- import { BeanControllerBase, deepEqual, IComponentOptions, Use } from 'zova';
6
+ import { BeanControllerBase, IComponentOptions, Use } from 'zova';
7
7
  import { Controller } from 'zova-module-a-bean';
8
8
  import { BeanBehaviorsHolder, IBehaviorItem } from 'zova-module-a-behavior';
9
9
 
10
10
  import type { ControllerForm } from '../form/controller.jsx';
11
11
 
12
+ import { schemaRefreshFingerprint } from '../../lib/schemaRefresh.js';
12
13
  import { TypeFormField } from '../../types/form.js';
13
14
  import {
14
15
  constFieldProps,
@@ -29,6 +30,11 @@ export class ControllerFormField<TParentData extends {} = {}> extends BeanContro
29
30
  static $componentOptions: IComponentOptions = { inheritAttrs: false };
30
31
 
31
32
  private _formField: TypeFormField;
33
+ private _initialized = false;
34
+ private _mounted = false;
35
+ private _disposed = false;
36
+ private _readyNotified = false;
37
+ private _remount = false;
32
38
  public propsBucket: IFormFieldRenderContextPropsBucket<TParentData>;
33
39
 
34
40
  @Use({ injectionScope: 'host' })
@@ -44,6 +50,7 @@ export class ControllerFormField<TParentData extends {} = {}> extends BeanContro
44
50
  // provide
45
51
  this.bean._setBean('$$formField', this);
46
52
  // field
53
+ this._remount = this.$$form.hasSchemaRefresh(this.name);
47
54
  this._formField = this._createField();
48
55
  // propsBucket
49
56
  this.propsBucket = this.$computed(() => {
@@ -51,18 +58,14 @@ export class ControllerFormField<TParentData extends {} = {}> extends BeanContro
51
58
  });
52
59
  // defaultValue
53
60
  // this._handleDefaultValue();
54
- // watch
61
+ this.$controllerMounted(() => {
62
+ this._mounted = true;
63
+ this._refreshField();
64
+ });
55
65
  this.$watch(
56
- () => this.property,
57
- (newValue, oldValue) => {
58
- if (deepEqual(newValue, oldValue)) return;
59
- // defaultValue
60
- // this._handleDefaultValue();
61
- const options = this._getFormFieldOptions();
62
- this._formField.api.update(options as any);
63
- this.form.resetField(this.name);
64
- this.$props.onFieldStateReady?.(this);
65
- },
66
+ () => [this.$$form.schemaRefresh, this._getRefreshConfiguration()],
67
+ () => this._refreshField(),
68
+ { flush: 'post' },
66
69
  );
67
70
  // behaviors
68
71
  await this.$$beanBehaviorsHolder.initialize({
@@ -81,7 +84,77 @@ export class ControllerFormField<TParentData extends {} = {}> extends BeanContro
81
84
  },
82
85
  );
83
86
  }
84
- this.$props.onFieldStateReady?.(this);
87
+ this._initialized = true;
88
+ this._refreshField();
89
+ }
90
+
91
+ protected __dispose__() {
92
+ this._disposed = true;
93
+ }
94
+
95
+ private _getRefreshConfiguration() {
96
+ const props = this.$props;
97
+ return schemaRefreshFingerprint([
98
+ props.name,
99
+ props.render,
100
+ props.options,
101
+ props.readonly,
102
+ props.required,
103
+ props.sys,
104
+ props.validators,
105
+ props.behaviors,
106
+ !!props.onEffect,
107
+ !!props.slotDefault,
108
+ ]);
109
+ }
110
+
111
+ private _refreshField() {
112
+ if (
113
+ !this._initialized ||
114
+ this._disposed ||
115
+ (process.env.CLIENT && this._remount && !this._mounted) ||
116
+ !this.$$form.schemaRefresh.contract.ready
117
+ ) {
118
+ return;
119
+ }
120
+ const props = this.$props;
121
+ // Basic Input owns its binding slot; direct custom slots remain opaque.
122
+ const inputBindingSlot =
123
+ props[constFieldProps] === true &&
124
+ (props.render
125
+ ? this.$$form.getRenderProvider(props.render)
126
+ : this.formProvider.components?.Input) === 'basic-input:formFieldInput';
127
+ const eligible =
128
+ !props.sys &&
129
+ !props.behaviors &&
130
+ !props.onEffect &&
131
+ (!props.slotDefault || inputBindingSlot) &&
132
+ Object.values(props.validators ?? {}).every(value => typeof value === 'boolean');
133
+ const refresh = this.$$form.consumeSchemaRefresh(
134
+ this.name,
135
+ this._getRefreshConfiguration(),
136
+ eligible,
137
+ );
138
+ if (refresh.changed) {
139
+ const value = this.form.getFieldValue(this.name);
140
+ this.field.api.update(this._getFormFieldOptions() as any);
141
+ this.form.resetField(this.name);
142
+ if (refresh.preserve) {
143
+ this.form.setFieldValue(this.name, value, {
144
+ dontUpdateMeta: true,
145
+ dontRunListeners: true,
146
+ dontValidate: true,
147
+ });
148
+ this.field.api.validateSync('change', {});
149
+ }
150
+ } else if (this._remount && !this._readyNotified) {
151
+ this.field.api.update(this._getFormFieldOptions() as any);
152
+ }
153
+ if (refresh.changed || !this._readyNotified) {
154
+ this._readyNotified = true;
155
+ this.$props.onFieldStateReady?.(this);
156
+ }
157
+ if (refresh.changed) this.$$form.schemaFieldRefreshed();
85
158
  }
86
159
 
87
160
  public get form() {
@@ -141,6 +214,8 @@ export class ControllerFormField<TParentData extends {} = {}> extends BeanContro
141
214
 
142
215
  private _createField() {
143
216
  const options = this._getFormFieldOptions();
217
+ // A remount must not seed over a live null/undefined before refresh eligibility is known.
218
+ if (this._remount) delete options.defaultValue;
144
219
  const field = markRaw(useField(options as any)) as any;
145
220
  const fieldState = useSelector(field.api.store, state => state) as any;
146
221
  return { api: field.api, state: fieldState };