@platforma-sdk/ui-vue 1.4.9 → 1.5.38

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platforma-sdk/ui-vue",
3
- "version": "1.4.9",
3
+ "version": "1.5.38",
4
4
  "type": "module",
5
5
  "main": "dist/lib.umd.cjs",
6
6
  "module": "dist/lib.js",
@@ -21,7 +21,7 @@
21
21
  "lru-cache": "^11.0.1",
22
22
  "vue": "^3.5.11",
23
23
  "canonicalize": "^2.0.0",
24
- "@milaboratories/uikit": "^1.2.16",
24
+ "@milaboratories/uikit": "^1.2.17",
25
25
  "@platforma-sdk/model": "^1.2.32"
26
26
  },
27
27
  "devDependencies": {
@@ -1,30 +1,7 @@
1
1
  import { reactive, computed, ref, watch, unref } from 'vue';
2
- import type { ZodError } from 'zod';
3
2
  import type { ModelOptions, Model } from './types';
4
3
  import { deepClone } from '@milaboratories/helpers';
5
- import { isJsonEqual } from './utils';
6
-
7
- const identity = <T, V = T>(v: T): V => v as unknown as V;
8
-
9
- const ensureError = (cause: unknown) => {
10
- if (cause instanceof Error) {
11
- return cause;
12
- }
13
-
14
- return Error(String(cause));
15
- };
16
-
17
- const isZodError = (err: Error): err is ZodError => {
18
- return err.name === 'ZodError';
19
- };
20
-
21
- const formatZodError = (err: ZodError) => {
22
- const { formErrors, fieldErrors } = err.flatten();
23
- const _fieldErrors = Object.entries(fieldErrors).map(([field, errors]) => {
24
- return field + ':' + errors?.join(',');
25
- });
26
- return formErrors.concat(_fieldErrors).join('; ');
27
- };
4
+ import { isJsonEqual, identity, ensureError, isZodError, formatZodError } from './utils';
28
5
 
29
6
  export function createModel<M, V = unknown>(options: ModelOptions<M, V>): Model<M> {
30
7
  const validate = options.validate ?? identity;
@@ -89,9 +66,9 @@ export function createModel<M, V = unknown>(options: ModelOptions<M, V>): Model<
89
66
 
90
67
  watch(
91
68
  local,
92
- (v, old) => {
93
- if (v && v === old) {
94
- setValue(v.model);
69
+ (n, o) => {
70
+ if (n && n === o) {
71
+ setValue(n.model);
95
72
  }
96
73
  },
97
74
  { deep: true },
@@ -4,9 +4,9 @@ import type { NavigationState, BlockOutputsBase, BlockState, Platforma } from '@
4
4
  import { reactive, nextTick, computed, watch } from 'vue';
5
5
  import type { UnwrapValueOrErrors, StateModelOptions, UnwrapOutputs, OptionalResult, OutputValues, OutputErrors } from '../types';
6
6
  import { createModel } from '../createModel';
7
+ import { createAppModel } from './createAppModel';
7
8
  import { parseQuery } from '../urls';
8
9
  import { MultiError, unwrapValueOrErrors } from '../utils';
9
- import { pick } from 'lodash';
10
10
 
11
11
  export function createApp<
12
12
  Args = unknown,
@@ -101,22 +101,23 @@ export function createApp<
101
101
  },
102
102
  });
103
103
  },
104
- createAppModel<T = AppModel>(options: StateModelOptions<AppModel, T> = {}) {
105
- return createModel<T, AppModel>({
106
- get() {
107
- if (options.transform) {
108
- return options.transform(snapshot);
109
- }
104
+ // @TODO currently disabled but will be used
105
+ // createAppModel<T extends AppModel = AppModel>(options: StateModelOptions<AppModel, T> = {}) {
106
+ // return createAppModel<T, AppModel>({
107
+ // get() {
108
+ // if (options.transform) {
109
+ // return options.transform(snapshot);
110
+ // }
110
111
 
111
- return { args: snapshot.args, ui: snapshot.ui } as T;
112
- },
113
- validate: options.validate,
114
- autoSave: true,
115
- onSave(newData) {
116
- setBlockArgsAndUiState(newData.args, newData.ui);
117
- },
118
- });
119
- },
112
+ // return { args: snapshot.args, ui: snapshot.ui } as T;
113
+ // },
114
+ // validate: options.validate,
115
+ // autoSave: true,
116
+ // onSave(newData) {
117
+ // setBlockArgsAndUiState(newData.args, newData.ui);
118
+ // },
119
+ // });
120
+ // },
120
121
  /**
121
122
  * Note: Don't forget to list the output names, like: useOutputs('output1', 'output2', ...etc)
122
123
  * @param keys - List of output names
@@ -212,28 +213,71 @@ export function createApp<
212
213
  },
213
214
  };
214
215
 
216
+ const outputs = computed<OutputValues<Outputs>>(() => {
217
+ const entries = Object.entries(snapshot.outputs).map(([k, vOrErr]) => [k, vOrErr.ok && vOrErr.value !== undefined ? vOrErr.value : undefined]);
218
+ return Object.fromEntries(entries);
219
+ });
220
+
221
+ const outputErrors = computed<OutputErrors<Outputs>>(() => {
222
+ const entries = Object.entries(snapshot.outputs).map(([k, vOrErr]) => [k, vOrErr && !vOrErr.ok ? new MultiError(vOrErr.errors) : undefined]);
223
+ return Object.fromEntries(entries);
224
+ });
225
+
215
226
  const getters = {
216
- args: computed(() => snapshot.args),
217
- outputs: computed(() => snapshot.outputs),
218
- ui: computed(() => snapshot.ui),
219
- navigationState: computed(() => snapshot.navigationState),
227
+ snapshot,
228
+ queryParams: computed(() => parseQuery<Href>(snapshot.navigationState.href)),
220
229
  href: computed(() => snapshot.navigationState.href),
230
+ hasErrors: computed(() => Object.values(snapshot.outputs).some((v) => !v?.ok)), // @TODO: there is middle-layer error, v sometimes is undefined
221
231
 
222
- outputValues: computed<OutputValues<Outputs>>(() => {
223
- const entries = Object.entries(snapshot.outputs).map(([k, vOrErr]) => [k, vOrErr.ok && vOrErr.value !== undefined ? vOrErr.value : undefined]);
224
- return Object.fromEntries(entries);
232
+ /** @deprecated */
233
+ outputValues: computed(() => {
234
+ console.warn('Change app.outputValues to app.model.outputs');
235
+ return outputs.value;
225
236
  }),
226
237
 
227
- outputErrors: computed<OutputErrors<Outputs>>(() => {
228
- const entries = Object.entries(snapshot.outputs).map(([k, vOrErr]) => [k, vOrErr && !vOrErr.ok ? new MultiError(vOrErr.errors) : undefined]);
229
- return Object.fromEntries(entries);
238
+ /** @deprecated */
239
+ outputErrors: computed(() => {
240
+ console.warn('Change app.outputErrors to app.model.outputErrors');
241
+ return outputErrors.value;
230
242
  }),
231
243
 
232
- queryParams: computed(() => parseQuery<Href>(snapshot.navigationState.href)),
233
- hasErrors: computed(() => Object.values(snapshot.outputs).some((v) => !v?.ok)), // @TODO: there is middle-layer error, v sometimes is undefined
244
+ /** @deprecated */
245
+ args: computed(() => {
246
+ console.warn('Change app.args to app.snapshot.args');
247
+ return snapshot.args;
248
+ }),
249
+ /** @deprecated */
250
+ outputs: computed(() => {
251
+ console.warn('Change app.outputs to app.snapshot.outputs');
252
+ return snapshot.outputs;
253
+ }),
254
+ /** @deprecated */
255
+ ui: computed(() => {
256
+ console.warn('Change app.ui to app.snapshot.ui');
257
+ return snapshot.ui;
258
+ }),
259
+ /** @deprecated */
260
+ navigationState: computed(() => {
261
+ console.warn('Change app.navigationState to app.snapshot.navigationState');
262
+ return snapshot.navigationState;
263
+ }),
234
264
  };
235
265
 
236
- const appModel = methods.createAppModel();
266
+ const appModel = createAppModel(
267
+ {
268
+ get() {
269
+ return { args: snapshot.args, ui: snapshot.ui } as AppModel;
270
+ },
271
+ autoSave: true,
272
+ onSave(newData: AppModel) {
273
+ setBlockArgsAndUiState(newData.args, newData.ui);
274
+ },
275
+ },
276
+ {
277
+ outputs,
278
+ outputErrors,
279
+ },
280
+ );
237
281
 
238
282
  return reactive(Object.assign(appModel, methods, getters));
239
283
  }
@@ -0,0 +1,100 @@
1
+ import { reactive, computed, ref, watch, unref, type ComputedRef, type UnwrapNestedRefs } from 'vue';
2
+ import type { ModelOptions, Model } from '../types';
3
+ import { deepClone } from '@milaboratories/helpers';
4
+ import { isJsonEqual, identity, ensureError, isZodError, formatZodError } from '../utils';
5
+
6
+ export function createAppModel<
7
+ M extends Record<string, unknown>,
8
+ V = unknown,
9
+ E extends Record<string, ComputedRef<unknown>> = Record<string, ComputedRef<unknown>>,
10
+ >(options: ModelOptions<M, V>, extended?: E): Model<M & UnwrapNestedRefs<E>> {
11
+ type R = M & UnwrapNestedRefs<E>;
12
+
13
+ const validate = options.validate ?? identity;
14
+
15
+ const { autoSave } = options;
16
+
17
+ const error = ref<Error | undefined>();
18
+
19
+ const local = ref<{ model: R }>();
20
+
21
+ const setSource = (v: M) => {
22
+ local.value = {
23
+ model: Object.assign(deepClone(v), extended ?? {}) as R,
24
+ };
25
+ };
26
+
27
+ watch(
28
+ () => options.get(),
29
+ (v) => setSource(v),
30
+ { immediate: true },
31
+ );
32
+
33
+ const save = () => {
34
+ options.onSave(validate(deepClone(local.value?.model)));
35
+ };
36
+
37
+ const revert = () => {
38
+ setSource(options.get());
39
+ error.value = undefined;
40
+ };
41
+
42
+ const setError = (cause: unknown) => {
43
+ const err = ensureError(cause);
44
+ if (isZodError(err)) {
45
+ error.value = Error(formatZodError(err)); // @todo temp
46
+ } else {
47
+ error.value = err;
48
+ }
49
+ };
50
+
51
+ const setValue = (v: M) => {
52
+ error.value = undefined;
53
+ try {
54
+ validate(v);
55
+ if (autoSave) {
56
+ save();
57
+ }
58
+ } catch (cause: unknown) {
59
+ setError(cause);
60
+ }
61
+ };
62
+
63
+ const model = computed<R>({
64
+ get: () => {
65
+ return local.value?.model as R;
66
+ },
67
+ set() {
68
+ throw Error('Cannot replace base model');
69
+ },
70
+ });
71
+
72
+ watch(
73
+ local,
74
+ (n, o) => {
75
+ if (n && n === o) {
76
+ setValue(n.model);
77
+ }
78
+ },
79
+ { deep: true },
80
+ );
81
+
82
+ const valid = computed(() => !error.value);
83
+
84
+ const isChanged = computed(() => {
85
+ return !isJsonEqual(options.get(), unref(local));
86
+ });
87
+
88
+ const errorString = computed(() => (error.value ? error.value.message : ''));
89
+
90
+ return reactive({
91
+ model,
92
+ valid,
93
+ isChanged,
94
+ error,
95
+ errorString,
96
+ save,
97
+ revert,
98
+ setError,
99
+ });
100
+ }
@@ -85,6 +85,8 @@ declare const _app: ExtApp;
85
85
 
86
86
  type _UpdateArgsParams = Parameters<Parameters<_App1['updateArgs']>[0]>[0];
87
87
 
88
+ type _ccc = _App1['model']['args'];
89
+
88
90
  type __cases = [
89
91
  Expect<Equal<Model<string>, typeof __model1>>,
90
92
  Expect<Equal<Model<number>, typeof __model2>>,
@@ -95,7 +97,8 @@ type __cases = [
95
97
  Expect<Equal<ExtApp['label'], string>>,
96
98
  Expect<Equal<ExtApp['method'], () => number>>,
97
99
  Expect<Equal<_App1['args'], Readonly<_Args>>>,
98
- Expect<Equal<_App1['outputValues']['sum'], number | undefined>>,
100
+ Expect<Equal<_App1['model']['outputs']['sum'], number | undefined>>,
101
+ Expect<Equal<_App1['model']['outputErrors']['sum'], Error | undefined>>,
99
102
  Expect<Equal<_App1['ui'], Readonly<_UiState>>>,
100
103
  Expect<Equal<_App1['model']['args'], _Args>>,
101
104
  Expect<Equal<_App1['model']['ui'], _UiState>>,
package/src/utils.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { ValueOrErrors } from '@platforma-sdk/model';
2
2
  import type { OptionalResult } from './types';
3
+ import type { ZodError } from 'zod';
3
4
  import canonicalize from 'canonicalize';
4
5
 
5
6
  export class UnresolvedError extends Error {}
@@ -62,3 +63,25 @@ export function isDefined<T>(v: T | undefined): v is T {
62
63
  export function isJsonEqual(a: unknown, b: unknown) {
63
64
  return canonicalize(a) === canonicalize(b);
64
65
  }
66
+
67
+ export const identity = <T, V = T>(v: T): V => v as unknown as V;
68
+
69
+ export const ensureError = (cause: unknown) => {
70
+ if (cause instanceof Error) {
71
+ return cause;
72
+ }
73
+
74
+ return Error(String(cause));
75
+ };
76
+
77
+ export const isZodError = (err: Error): err is ZodError => {
78
+ return err.name === 'ZodError';
79
+ };
80
+
81
+ export const formatZodError = (err: ZodError) => {
82
+ const { formErrors, fieldErrors } = err.flatten();
83
+ const _fieldErrors = Object.entries(fieldErrors).map(([field, errors]) => {
84
+ return field + ':' + errors?.join(',');
85
+ });
86
+ return formErrors.concat(_fieldErrors).join('; ');
87
+ };