ballerina-core 1.0.30 → 1.0.31

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/main.ts CHANGED
@@ -91,6 +91,8 @@ export * from "./src/forms/domains/primitives/domains/searchable-infinite-stream
91
91
  export * from "./src/forms/domains/primitives/domains/searchable-infinite-stream-multiselect/template"
92
92
  export * from "./src/forms/domains/primitives/domains/list/state"
93
93
  export * from "./src/forms/domains/primitives/domains/list/template"
94
+ export * from "./src/forms/domains/primitives/domains/map/state"
95
+ export * from "./src/forms/domains/primitives/domains/map/template"
94
96
  export * from "./src/forms/domains/singleton/domains/mapping/state"
95
97
  export * from "./src/forms/domains/singleton/domains/mapping/template"
96
98
  export * from "./src/forms/domains/parser/state"
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "ballerina-core",
3
3
  "author": "Dr. Giuseppe Maggiore",
4
4
  "private": false,
5
- "version": "1.0.30",
5
+ "version": "1.0.31",
6
6
  "main": "main.ts",
7
7
  "dependencies": {
8
8
  "immutable": "^5.0.0-beta.5",
@@ -1,3 +1,4 @@
1
+ import { Sum } from "../../../../../../../../main";
1
2
  import { DebouncedStatus, DirtyStatus } from "../../../../../../../debounced/state";
2
3
  import { AsyncState } from "../../../../../../state";
3
4
  import { CollectionEntity } from "../../../collection/state";
@@ -7,7 +8,7 @@ export type LoadedCollectionEntity<E> = LoadedEntity<E> & {
7
8
  isRemoved:boolean;
8
9
  lastUpdated: number;
9
10
  dirty: DirtyStatus;
10
- status: DebouncedStatus;
11
+ status: Sum<DebouncedStatus, "debug off">;
11
12
  };
12
13
  export const LoadedCollectionEntity = {
13
14
  Default:{
@@ -1,3 +1,4 @@
1
+ import { Sum } from "../../../../../../../../main";
1
2
  import { DebouncedStatus, DirtyStatus } from "../../../../../../../debounced/state";
2
3
  import { AsyncState } from "../../../../../../state";
3
4
  import { Entity } from "../../state";
@@ -10,7 +11,7 @@ export type LoadedEntity<E> = {
10
11
  synchronizationErrors:Array<any>;
11
12
  lastUpdated: number;
12
13
  dirty: DirtyStatus;
13
- status: DebouncedStatus;
14
+ status: Sum<DebouncedStatus, "debug off">;
14
15
  };
15
16
  export const LoadedEntity = {
16
17
  Default:{
@@ -26,11 +26,18 @@ export const Debounce = <value, context = Unit>(
26
26
  Co.SetState(
27
27
  updaters.Core.dirty(replaceWith<DirtyStatus>("dirty but being processed"))
28
28
  ),
29
- Co.SetState(
30
- updaters.Core.status(
31
- replaceWith<DebouncedStatus>("just detected dirty, starting processing")
32
- )
33
- ),
29
+ Co.GetState().then((current) => {
30
+ if (current.status.kind == "l") {
31
+ return Co.SetState(
32
+ updaters.Core.status(
33
+ replaceWith<DebouncedStatus>(
34
+ "just detected dirty, starting processing"
35
+ )
36
+ )
37
+ );
38
+ }
39
+ else return Co.Do(() => {});
40
+ }),
34
41
  Co.Any([
35
42
  // shortcircuit the validation if it takes longer than the whole cycle in the presence of an underwater update of the field
36
43
  Co.Seq([
@@ -40,22 +47,36 @@ export const Debounce = <value, context = Unit>(
40
47
  Date.now() - current.lastUpdated <= debounceDurationInMs,
41
48
  Co.Wait(debounceDurationInMs / 5)
42
49
  ),
43
- Co.SetState(
44
- updaters.Core.status(
45
- replaceWith<DebouncedStatus>("processing shortcircuited")
46
- )
47
- ),
50
+ Co.GetState().then((current) => {
51
+ if (current.status.kind == "l") {
52
+ return Co.SetState(
53
+ updaters.Core.status(
54
+ replaceWith<DebouncedStatus>(
55
+ "processing shortcircuited"
56
+ )
57
+ )
58
+ );
59
+ }
60
+ else return Co.Do(() => {});
61
+ }),
48
62
  Co.Wait(debounceDurationInMs / 2),
49
63
  ]),
50
64
  k
51
65
  .embed((_: Debounced<value & context>) => _, updaters.Core.value)
52
66
  .then((apiResult) => {
53
67
  return Co.Seq([
54
- Co.SetState(
55
- updaters.Core.status(
56
- replaceWith<DebouncedStatus>("processing finished")
57
- )
58
- ),
68
+ Co.GetState().then((current) => {
69
+ if (current.status.kind == "l") {
70
+ return Co.SetState(
71
+ updaters.Core.status(
72
+ replaceWith<DebouncedStatus>(
73
+ "processing finished"
74
+ )
75
+ )
76
+ );
77
+ }
78
+ else return Co.Do(() => {});
79
+ }),
59
80
  // Co.Wait(250)
60
81
  ]).then(() => {
61
82
  return Co.GetState().then((current) => {
@@ -64,13 +85,18 @@ export const Debounce = <value, context = Unit>(
64
85
  // maybe a new change has already reset dirty, in that case we need to start all over again
65
86
  if (current.dirty == "dirty but being processed") {
66
87
  return Co.Seq([
67
- Co.SetState(
68
- updaters.Core.status(
69
- replaceWith<DebouncedStatus>(
70
- "state was still dirty but being processed, resetting to not dirty"
71
- )
72
- )
73
- ),
88
+ Co.GetState().then((current) => {
89
+ if (current.status.kind == "l") {
90
+ return Co.SetState(
91
+ updaters.Core.status(
92
+ replaceWith<DebouncedStatus>(
93
+ "state was still dirty but being processed, resetting to not dirty"
94
+ )
95
+ )
96
+ );
97
+ }
98
+ else return Co.Do(() => {});
99
+ }),
74
100
  // use UpdateState to make sure that we look up the state at the last possible moment to account for delays
75
101
  Co.UpdateState((state) =>
76
102
  state.dirty == "dirty but being processed"
@@ -82,13 +108,18 @@ export const Debounce = <value, context = Unit>(
82
108
  ]);
83
109
  } else {
84
110
  return Co.Seq([
85
- Co.SetState(
86
- updaters.Core.status(
87
- replaceWith<DebouncedStatus>(
88
- "state was changed underwater back to dirty, leaving the dirty flag alone"
89
- )
90
- )
91
- ),
111
+ Co.GetState().then((current) => {
112
+ if (current.status.kind == "l") {
113
+ return Co.SetState(
114
+ updaters.Core.status(
115
+ replaceWith<DebouncedStatus>(
116
+ "state was changed underwater back to dirty, leaving the dirty flag alone"
117
+ )
118
+ )
119
+ );
120
+ }
121
+ else return Co.Do(() => {});
122
+ }),
92
123
  Co.SetState(
93
124
  updaters.Core.dirty(replaceWith<DirtyStatus>("dirty"))
94
125
  ),
@@ -97,13 +128,18 @@ export const Debounce = <value, context = Unit>(
97
128
  }
98
129
  } else {
99
130
  return Co.Seq([
100
- Co.SetState(
101
- updaters.Core.status(
102
- replaceWith<DebouncedStatus>(
103
- "inner call failed with transient failure"
104
- )
105
- )
106
- ),
131
+ Co.GetState().then((current) => {
132
+ if (current.status.kind == "l") {
133
+ return Co.SetState(
134
+ updaters.Core.status(
135
+ replaceWith<DebouncedStatus>(
136
+ "inner call failed with transient failure"
137
+ )
138
+ )
139
+ );
140
+ }
141
+ else return Co.Do(() => {});
142
+ }),
107
143
  Co.SetState(
108
144
  updaters.Core.dirty(replaceWith<DirtyStatus>("dirty"))
109
145
  ),
@@ -1,3 +1,4 @@
1
+ import { Sum } from "../../main";
1
2
  import { replaceWith } from "../fun/domains/updater/domains/replaceWith/state";
2
3
  import { BasicUpdater, Updater } from "../fun/domains/updater/state";
3
4
 
@@ -7,19 +8,19 @@ export type DebouncedStatus = "waiting for dirty" | "just detected dirty, starti
7
8
  | "processing shortcircuited"
8
9
  | "state was changed underwater back to dirty, leaving the dirty flag alone"
9
10
  | "inner call failed with transient failure"
10
- export type Debounced<Value> = Value & { lastUpdated: number; dirty: DirtyStatus; status:DebouncedStatus };
11
+ export type Debounced<Value> = Value & { lastUpdated: number; dirty: DirtyStatus; status: Sum<DebouncedStatus, "debug off"> };
11
12
  export const Debounced = {
12
- Default: <v>(initialValue: v): Debounced<v> => ({
13
+ Default: <v>(initialValue: v, debug?: boolean): Debounced<v> => ({
13
14
  ...initialValue,
14
15
  lastUpdated: 0,
15
16
  dirty: "not dirty",
16
- status: "waiting for dirty"
17
+ status: debug ? Sum.Default.left("waiting for dirty") : Sum.Default.right("debug off")
17
18
  }),
18
19
  Updaters: {
19
20
  Core:{
20
21
  status: <v>(_: BasicUpdater<DebouncedStatus>): Updater<Debounced<v>> => Updater<Debounced<v>>(current => ({
21
22
  ...current,
22
- status: _(current.status),
23
+ status: current.status.kind == "l" ? Sum.Default.left(_(current.status.value)) : Sum.Default.right("debug off"),
23
24
  })),
24
25
  dirty: <v>(_: BasicUpdater<DirtyStatus>): Updater<Debounced<v>> => Updater<Debounced<v>>(current => ({
25
26
  ...current,
@@ -23,7 +23,7 @@ export const editFormRunner = <E, FS>() => {
23
23
 
24
24
  const init = Co.GetState().then((current) =>
25
25
  Synchronize<Unit, E>(
26
- () => current.api.get(),
26
+ () => current.api.get(current.entityId),
27
27
  (_) => "transient failure",
28
28
  5,
29
29
  50
@@ -1,4 +1,4 @@
1
- import { ApiResponseChecker, AsyncState, BasicUpdater, Debounced, ForeignMutationsInput, id, SimpleCallback, simpleUpdater, Synchronized, Template, unit, Unit, Updater, Value } from "../../../../../../main"
1
+ import { ApiResponseChecker, AsyncState, BasicUpdater, Debounced, ForeignMutationsInput, Guid, id, SimpleCallback, simpleUpdater, Synchronized, Template, unit, Unit, Updater, Value } from "../../../../../../main"
2
2
  import { BasicFun } from "../../../../../fun/state"
3
3
 
4
4
  export type ApiErrors = Array<string>
@@ -6,7 +6,7 @@ export type ApiErrors = Array<string>
6
6
  export type EditFormContext<E,FS> = {
7
7
  entityId:string,
8
8
  api:{
9
- get:() => Promise<E>,
9
+ get:(id: Guid) => Promise<E>,
10
10
  update:BasicFun<E, Promise<ApiErrors>>
11
11
  },
12
12
  actualForm:Template<Value<E> & FS, FS, { onChange:SimpleCallback<BasicUpdater<E>>}>
@@ -16,7 +16,8 @@ export type PrimitiveType = (typeof PrimitiveTypes)[number]
16
16
  export const GenericTypes = [
17
17
  "SingleSelection",
18
18
  "MultiSelection",
19
- "List"] as const
19
+ "List",
20
+ "Map"] as const
20
21
  export type GenericType = (typeof GenericTypes)[number]
21
22
 
22
23
  export type ApiConverter<T> = { fromAPIRawValue: BasicFun<any, T>, toAPIRawValue: BasicFun<T, any> }
@@ -30,6 +31,7 @@ export type ApiConverters = {
30
31
  "SingleSelection": ApiConverter<CollectionSelection<any>>
31
32
  "MultiSelection": ApiConverter<OrderedMap<string, any>>
32
33
  "List": ApiConverter<List<any>>
34
+ "Map": ApiConverter<List<[any, any]>>
33
35
  }
34
36
 
35
37
  export type PrimitiveBuiltIn = { renderers: Set<keyof BuiltIns["renderers"]>, apiConverters: ApiConverter<any>, defaultValue: any }
@@ -48,6 +50,7 @@ export type BuiltIns = {
48
50
  streamSingleSelection: Set<string>;
49
51
  streamMultiSelection: Set<string>;
50
52
  list: Set<string>;
53
+ map: Set<string>;
51
54
  };
52
55
  };
53
56
 
@@ -65,7 +68,8 @@ export const builtInsFromFieldViews = (fieldViews: any, fieldTypeConverters: Api
65
68
  "generics": Map([
66
69
  ["SingleSelection", { apiConverters: fieldTypeConverters["SingleSelection"], defaultValue: CollectionSelection().Default.right("no selection") }] as [string, GenericBuiltIn],
67
70
  ["Multiselection", { apiConverters: fieldTypeConverters["SingleSelection"], defaultValue: Map() }] as [string, GenericBuiltIn],
68
- ["List", { apiConverters: fieldTypeConverters["SingleSelection"], defaultValue: List() }] as [string, GenericBuiltIn]
71
+ ["List", { apiConverters: fieldTypeConverters["SingleSelection"], defaultValue: List() }] as [string, GenericBuiltIn],
72
+ ["Map", { apiConverters: fieldTypeConverters["SingleSelection"], defaultValue: List() }] as [string, GenericBuiltIn]
69
73
  ]),
70
74
  "renderers": {
71
75
  "boolean": Set(),
@@ -78,6 +82,7 @@ export const builtInsFromFieldViews = (fieldViews: any, fieldTypeConverters: Api
78
82
  "number": Set(),
79
83
  "string": Set(),
80
84
  "list": Set(),
85
+ "map": Set(),
81
86
  }
82
87
  }
83
88
  Object.keys(builtins.renderers).forEach((_categoryName) => {
@@ -92,31 +97,53 @@ export const builtInsFromFieldViews = (fieldViews: any, fieldTypeConverters: Api
92
97
  }
93
98
 
94
99
 
95
- export const defaultValue = (types:Map<TypeName, TypeDefinition>, builtIns:BuiltIns) => (t: TypeName): any => {
96
- let primitive = builtIns.primitives.get(t)
97
- if (primitive != undefined) {
98
- return primitive.defaultValue
99
- } else {
100
- let generic = builtIns.generics.get(t)
101
- if (generic != undefined) {
102
- return generic.defaultValue
100
+ export const defaultValue = (types: Map<TypeName, TypeDefinition>, builtIns: BuiltIns) => (t: TypeName | Type): any => {
101
+ if (typeof t == "string") {
102
+ let primitive = builtIns.primitives.get(t)
103
+ if (primitive != undefined) {
104
+ return primitive.defaultValue
103
105
  } else {
104
- let custom = types.get(t)
105
- if (custom != undefined) {
106
- let res = {} as any
107
- custom.fields.forEach((field, fieldName) => {
108
- res[fieldName] = defaultValue(types, builtIns)(field.kind == "primitive" ? field.value : field.kind == "lookup" ? field.name : field.value)
106
+ let generic = builtIns.generics.get(t)
107
+ if (generic != undefined) {
108
+ return generic.defaultValue
109
+ } else {
110
+ let custom = types.get(t)
111
+ if (custom != undefined) {
112
+ let res = {} as any
113
+ custom.fields.forEach((field, fieldName) => {
114
+ res[fieldName] = defaultValue(types, builtIns)(field.kind == "primitive" ? field.value : field.kind == "lookup" ? field.name : field.value)
115
+ }
116
+ )
117
+ return res
118
+ } else {
119
+ debugger
120
+ throw `cannot find type ${t} when resolving defaultValue`
109
121
  }
110
- )
122
+ }
123
+ }
124
+ } else {
125
+ if (t.kind == "application") {
126
+ const generic = builtIns.generics.get(t.value)
127
+ if (generic) {
128
+ const res = generic.defaultValue
111
129
  return res
112
- } else {
113
- throw `cannot find type ${t} when resolving defaultValue`
114
130
  }
115
131
  }
132
+ debugger
133
+ throw `cannot find type ${t} when resolving defaultValue`
134
+ }
135
+ }
136
+
137
+
138
+ const parseTypeIShouldBePartOfFormValidation = (t:any) : TypeName | Type => {
139
+ if (typeof t == "string") return t
140
+ if ("fun" in t && "args" in t && Array.isArray(t.args)) {
141
+ return { kind:"application", value:t.fun, args:t.args }
116
142
  }
143
+ return null!
117
144
  }
118
145
 
119
- export const fromAPIRawValue = (t:Type, types:Map<TypeName, TypeDefinition>, builtIns:BuiltIns, converters:ApiConverters, isKeywordsReplaced: boolean = false) => (raw:any) : any => {
146
+ export const fromAPIRawValue = (t: Type, types: Map<TypeName, TypeDefinition>, builtIns: BuiltIns, converters: ApiConverters, isKeywordsReplaced: boolean = false) => (raw: any): any => {
120
147
  // alert(JSON.stringify(t))
121
148
  if (raw == undefined) {
122
149
  console.warn(`instantiating default value for type ${JSON.stringify(t)}: the value was undefined so something is missing from the API response`)
@@ -131,25 +158,49 @@ export const fromAPIRawValue = (t:Type, types:Map<TypeName, TypeDefinition>, bui
131
158
  if (t.value == "SingleSelection" && t.args.length == 1) {
132
159
  let result = converters[t.value].fromAPIRawValue(obj)
133
160
  result = CollectionSelection().Updaters.left(
134
- fromAPIRawValue({ kind:"lookup", name:t.args[0] }, types, builtIns, converters, true))(result)
161
+ fromAPIRawValue({ kind: "lookup", name: t.args[0] }, types, builtIns, converters, true))(result)
135
162
  return result
136
163
  }
137
164
  if ((t.value == "Multiselection" || t.value == "MultiSelection") && t.args.length == 1) {
138
165
  let result = converters["MultiSelection"].fromAPIRawValue(obj)
139
- result = result.map(fromAPIRawValue({ kind:"lookup", name:t.args[0] }, types, builtIns, converters, true))
166
+ result = result.map(fromAPIRawValue({ kind: "lookup", name: t.args[0] }, types, builtIns, converters, true))
140
167
  return result
141
168
  }
142
169
  if (t.value == "List" && t.args.length == 1) {
143
170
  let result = converters[t.value].fromAPIRawValue(obj)
144
171
  result = result.map(fromAPIRawValue(
145
172
  PrimitiveTypes.some(_ => _ == t.args[0]) ?
146
- { kind:"primitive", value:t.args[0] as PrimitiveType }
147
- : { kind:"lookup", name:t.args[0] }
173
+ { kind: "primitive", value: t.args[0] as PrimitiveType }
174
+ : { kind: "lookup", name: t.args[0] }
148
175
  , types, builtIns, converters, true))
149
176
  return result
150
177
  }
178
+ if (t.value == "Map" && t.args.length == 2) {
179
+ let result = converters[t.value].fromAPIRawValue(obj)
180
+ let t_args = t.args.map(parseTypeIShouldBePartOfFormValidation)
181
+ result = result.map(keyValue => ([
182
+ fromAPIRawValue(
183
+ typeof t_args[0] == "string" ?
184
+ PrimitiveTypes.some(_ => _ == t_args[0]) ?
185
+ { kind: "primitive", value: t_args[0] as PrimitiveType }
186
+ : { kind: "lookup", name: t_args[0] }
187
+ :
188
+ t_args[0],
189
+ types, builtIns, converters, true)(keyValue[0]),
190
+ fromAPIRawValue(
191
+ typeof t_args[1] == "string" ?
192
+ PrimitiveTypes.some(_ => _ == t_args[1]) ?
193
+ { kind: "primitive", value: t_args[1] as PrimitiveType }
194
+ : { kind: "lookup", name: t_args[1] }
195
+ :
196
+ t_args[1],
197
+ types, builtIns, converters, true)(keyValue[1]),
198
+ ])
199
+ )
200
+ return result
201
+ }
151
202
  } else { // t.kind == lookup: we are dealing with a record/object
152
- let result:any = {...obj}
203
+ let result: any = { ...obj }
153
204
  const tDef = types.get(t.name)!
154
205
  tDef.fields.forEach((fieldType, fieldName) => {
155
206
  const replacedFieldName = replaceKeyword(fieldName)
@@ -163,8 +214,8 @@ export const fromAPIRawValue = (t:Type, types:Map<TypeName, TypeDefinition>, bui
163
214
  }
164
215
 
165
216
 
166
- export const toAPIRawValue = (t:Type, types:Map<TypeName, TypeDefinition>, builtIns:BuiltIns, converters:ApiConverters, isKeywordsReverted: boolean = false) => (raw:any) : any => {
167
-
217
+ export const toAPIRawValue = (t: Type, types: Map<TypeName, TypeDefinition>, builtIns: BuiltIns, converters: ApiConverters, isKeywordsReverted: boolean = false) => (raw: any): any => {
218
+
168
219
  const obj = !isKeywordsReverted ? replaceKeywords(raw, "to api") : raw
169
220
 
170
221
  if (t.kind == "primitive") {
@@ -173,7 +224,7 @@ export const toAPIRawValue = (t:Type, types:Map<TypeName, TypeDefinition>, built
173
224
  if (t.value == "SingleSelection" && t.args.length == 1) {
174
225
  let result = converters[t.value].toAPIRawValue(obj)
175
226
  if (result != undefined && typeof result == "object")
176
- result = toAPIRawValue({ kind:"lookup", name:t.args[0] }, types, builtIns, converters, true)(result)
227
+ result = toAPIRawValue({ kind: "lookup", name: t.args[0] }, types, builtIns, converters, true)(result)
177
228
  return result
178
229
  }
179
230
  if ((t.value == "Multiselection" || t.value == "MultiSelection") && t.args.length == 1) {
@@ -181,8 +232,8 @@ export const toAPIRawValue = (t:Type, types:Map<TypeName, TypeDefinition>, built
181
232
  let result = converters["MultiSelection"].toAPIRawValue(obj)
182
233
  // alert(`MultiSelect result1 = ${JSON.stringify(result)}`)
183
234
  // alert(`${JSON.stringify(t.args[0])}`)
184
- result = result.map((_:any) =>
185
- typeof _ == "object" ? toAPIRawValue({ kind:"lookup", name:t.args[0] }, types, builtIns, converters, true)(_) : _)
235
+ result = result.map((_: any) =>
236
+ typeof _ == "object" ? toAPIRawValue({ kind: "lookup", name: t.args[0] }, types, builtIns, converters, true)(_) : _)
186
237
  // alert(`MultiSelect result2 = ${JSON.stringify(result)}`)
187
238
  return result
188
239
  }
@@ -190,14 +241,41 @@ export const toAPIRawValue = (t:Type, types:Map<TypeName, TypeDefinition>, built
190
241
  let result = converters[t.value].toAPIRawValue(obj)
191
242
  result = result.map(toAPIRawValue(
192
243
  PrimitiveTypes.some(_ => _ == t.args[0]) ?
193
- { kind:"primitive", value:t.args[0] as PrimitiveType }
194
- : { kind:"lookup", name:t.args[0] },
244
+ { kind: "primitive", value: t.args[0] as PrimitiveType }
245
+ : { kind: "lookup", name: t.args[0] },
195
246
  // { kind:"lookup", name:t.args[0] },
196
247
  types, builtIns, converters, true))
197
248
  return result
198
249
  }
250
+ if (t.value == "Map" && t.args.length == 2) {
251
+ let result = converters[t.value].toAPIRawValue(obj)
252
+
253
+ let t_args = t.args.map(parseTypeIShouldBePartOfFormValidation)
254
+ result = result.map((keyValue: any) => ([
255
+ toAPIRawValue(
256
+ typeof t_args[0] == "string" ?
257
+ PrimitiveTypes.some(_ => _ == t_args[0]) ?
258
+ { kind: "primitive", value: t_args[0] as PrimitiveType }
259
+ : { kind: "lookup", name: t_args[0] }
260
+ :
261
+ t_args[0],
262
+ types, builtIns, converters, true)(keyValue[0]),
263
+ toAPIRawValue(
264
+ typeof t_args[1] == "string" ?
265
+ PrimitiveTypes.some(_ => _ == t_args[1]) ?
266
+ { kind: "primitive", value: t_args[1] as PrimitiveType }
267
+ : { kind: "lookup", name: t_args[1] }
268
+ :
269
+ t_args[1],
270
+ types, builtIns, converters, true)(keyValue[1]),
271
+ ])
272
+ )
273
+
274
+ return result
275
+ }
276
+
199
277
  } else { // t.kind == lookup: we are dealing with a record/object
200
- let result:any = {...obj}
278
+ let result: any = { ...obj }
201
279
  const tDef = types.get(t.name)!
202
280
  tDef.fields.forEach((fieldType, fieldName) => {
203
281
  const revertedFieldName = revertKeyword(fieldName)
@@ -206,5 +284,5 @@ export const toAPIRawValue = (t:Type, types:Map<TypeName, TypeDefinition>, built
206
284
  })
207
285
  return result
208
286
  }
209
- return defaultValue(types, builtIns)(t.value)
287
+ return defaultValue(types, builtIns)(t.value)
210
288
  }
@@ -1,4 +1,5 @@
1
- import { OrderedMap } from "immutable";
1
+ import { Map, OrderedMap } from "immutable";
2
+ import { BuiltIns } from "../built-ins/state";
2
3
 
3
4
  export type FieldName = string;
4
5
  export type TypeName = string;
@@ -7,16 +8,19 @@ export type TypeDefinition = {
7
8
  name: TypeName;
8
9
  fields: OrderedMap<FieldName, Type>;
9
10
  };
11
+ export type PrimitiveTypeName = "string" | "number" | "maybeBoolean" | "boolean" | "Date" | "CollectionReference"
10
12
  export type Type = {
11
13
  kind: "lookup"; name: TypeName;
12
14
  } | {
13
- kind: "primitive"; value: "string" | "number" | "maybeBoolean" | "boolean" | "Date" | "CollectionReference";
15
+ kind: "primitive"; value: PrimitiveTypeName;
14
16
  } | {
15
- kind: "application"; value: TypeName; args: Array<TypeName>;
17
+ kind: "application"; value: TypeName; args: Array<TypeName>; // args: Array<TypeName | Type>;
16
18
  };
17
19
  export const Type = {
18
20
  Default: {
19
- lookup: (name: TypeName): Type => ({ kind: "lookup", name })
21
+ lookup: (name: TypeName): Type => ({ kind: "lookup", name }),
22
+ primitive: (name: PrimitiveTypeName): Type => ({ kind: "primitive", value: name }),
23
+ application: (value: TypeName, args: Array<TypeName>): Type => ({ kind: "application", value, args }),
20
24
  },
21
25
  Operations: {
22
26
  Equals: (fst: Type, snd: Type): boolean =>
@@ -26,6 +30,14 @@ export const Type = {
26
30
  fst.value == snd.value &&
27
31
  fst.args.length == snd.args.length &&
28
32
  fst.args.every((v, i) => v == snd.args[i]) :
29
- false
33
+ false,
34
+ FromName: (types: Map<string, TypeDefinition>, builtIns: BuiltIns) => (typeName: string): Type | undefined => {
35
+ const recordTypeName = types.get(typeName)?.name
36
+ if (recordTypeName) return Type.Default.lookup(recordTypeName)
37
+ const primitiveTypeName = builtIns.primitives.get(typeName) && typeName
38
+ if (primitiveTypeName) return Type.Default.primitive(primitiveTypeName as any)
39
+ return undefined
40
+ }
30
41
  }
31
42
  }
43
+
@@ -6,6 +6,7 @@ export type FieldConfig = {
6
6
  label: string
7
7
  api: { stream?: string, enumOptions?: string },
8
8
  elementRenderer?: string,
9
+ mapRenderer?: { keyRenderer: FieldConfig, valueRenderer: FieldConfig },
9
10
  visible: BoolExpr<any>;
10
11
  disabled: BoolExpr<any>;
11
12
  };
@@ -98,8 +99,10 @@ export const FormsConfig = {
98
99
  } else if (typeof configFieldType == "object") {
99
100
  if ("fun" in configFieldType && "args" in configFieldType &&
100
101
  typeof configFieldType["fun"] == "string" &&
101
- Array.isArray(configFieldType["args"]) &&
102
- configFieldType["args"].every(_ => typeof (_) == "string")) {
102
+ Array.isArray(configFieldType["args"])
103
+ // &&
104
+ // configFieldType["args"].every(_ => typeof (_) == "string")
105
+ ) {
103
106
  const fieldType: Type = {
104
107
  kind: "application",
105
108
  value: configFieldType["fun"] as any,
@@ -122,10 +125,10 @@ export const FormsConfig = {
122
125
  errors.push(`field ${fieldName} of type ${typeName} is non-existent primitive type ${fieldDef.value}`);
123
126
  if (fieldDef.kind == "lookup" && !types.has(fieldDef.name))
124
127
  errors.push(`field ${fieldName} of type ${typeName} is non-existent type ${fieldDef.name}`);
125
- if (fieldDef.kind == "application" && !builtIns.generics.has(fieldDef.value))
126
- errors.push(`field ${fieldName} of type ${typeName} applies non-existent generic type ${fieldDef.value}`);
127
- if (fieldDef.kind == "application" && fieldDef.args.some(argType => !builtIns.primitives.has(argType) && !types.has(argType)))
128
- errors.push(`field ${fieldName} of type ${typeName} applies non-existent type arguments ${JSON.stringify(fieldDef.args.filter(argType => !builtIns.primitives.has(argType) && !types.has(argType)))}`);
128
+ // if (fieldDef.kind == "application" && !builtIns.generics.has(fieldDef.value))
129
+ // errors.push(`field ${fieldName} of type ${typeName} applies non-existent generic type ${fieldDef.value}`);
130
+ // if (fieldDef.kind == "application" && fieldDef.args.some(argType => !builtIns.primitives.has(argType) && !types.has(argType)))
131
+ // errors.push(`field ${fieldName} of type ${typeName} applies non-existent type arguments ${JSON.stringify(fieldDef.args.filter(argType => !builtIns.primitives.has(argType) && !types.has(argType)))}`);
129
132
  if (fieldDef.kind == "application" && fieldDef.value == "SingleSelection") {
130
133
  if (fieldDef.args.length != 1)
131
134
  errors.push(`field ${fieldName} in type ${typeName}: SingleSelection should have exactly one type argument, found ${JSON.stringify(fieldDef.args)}`);
@@ -259,7 +262,7 @@ export const FormsConfig = {
259
262
 
260
263
  let forms: Map<string, FormDef> = Map();
261
264
  Object.keys(formsConfig["forms"]).forEach((formName: any) => {
262
- let formDef: FormDef = { name: formName, type: "", fields: Map(), tabs: Map(), typeDef:null! };
265
+ let formDef: FormDef = { name: formName, type: "", fields: Map(), tabs: Map(), typeDef: null! };
263
266
  forms = forms.set(formName, formDef);
264
267
  const configFormDef = formsConfig["forms"][formName];
265
268
  if ("type" in configFormDef == false) {
@@ -288,74 +291,116 @@ export const FormsConfig = {
288
291
  errors.push(`field ${fieldName} of form ${formName} has no 'renderer' attribute`);
289
292
  }
290
293
  const fieldTypeDef = formTypeDef?.fields.get(fieldName)
291
- if (fieldTypeDef?.kind == "primitive") {
292
- if (fieldTypeDef.value == "maybeBoolean") {
293
- // alert(JSON.stringify(fieldConfig["renderer"]))
294
- // alert(JSON.stringify(builtIns.renderers.MaybeBooleanViews))
295
- if (!builtIns.renderers.maybeBoolean.has(fieldConfig["renderer"]))
296
- errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
297
- } else if (fieldTypeDef.value == "boolean") {
298
- if (!builtIns.renderers.boolean.has(fieldConfig["renderer"]))
299
- errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
300
- } else if (fieldTypeDef.value == "number") {
301
- if (!builtIns.renderers.number.has(fieldConfig["renderer"]))
302
- errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
303
- } else if (fieldTypeDef.value == "string") {
304
- if (!builtIns.renderers.string.has(fieldConfig["renderer"]))
305
- errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
306
- } else if (fieldTypeDef.value == "Date") {
307
- if (!builtIns.renderers.date.has(fieldConfig["renderer"])) {
308
- alert(JSON.stringify(builtIns.renderers))
309
- errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
310
- }
311
- } else {
312
- errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
313
- }
314
- } else if (fieldTypeDef?.kind == "application") {
315
- if (fieldTypeDef?.value == "SingleSelection") {
316
- if (!builtIns.renderers.enumSingleSelection.has(fieldConfig["renderer"]) &&
317
- !builtIns.renderers.streamSingleSelection.has(fieldConfig["renderer"]))
318
- errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
319
- } else if (fieldTypeDef?.value == "Multiselection") {
320
- if (!builtIns.renderers.enumMultiSelection.has(fieldConfig["renderer"]) &&
321
- !builtIns.renderers.streamMultiSelection.has(fieldConfig["renderer"]))
322
- errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
323
- } else if (fieldTypeDef?.value == "List") {
324
- if (!builtIns.renderers.list.has(fieldConfig["renderer"]))
325
- errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
326
- }
327
- if (fieldTypeDef.args.length < 1)
328
- errors.push(`field ${fieldName} of form ${formName} should have one type argument}`);
329
- else if (
330
- (builtIns.renderers.list.has(fieldConfig["renderer"])) && "elementRenderer" in fieldConfig != true)
331
- errors.push(`field ${fieldName} of form ${formName} is missing the 'elementRenderer' property`);
332
- else if (
333
- (builtIns.renderers.enumMultiSelection.has(fieldConfig["renderer"]) ||
334
- builtIns.renderers.enumSingleSelection.has(fieldConfig["renderer"])) && "options" in fieldConfig != true)
335
- errors.push(`field ${fieldName} of form ${formName} is missing the 'options' property`);
336
- else if (
337
- (builtIns.renderers.streamSingleSelection.has(fieldConfig["renderer"]) ||
338
- builtIns.renderers.streamMultiSelection.has(fieldConfig["renderer"])) && "stream" in fieldConfig != true)
339
- errors.push(`field ${fieldName} of form ${formName} is missing the 'stream' property`);
340
- else if ((builtIns.renderers.enumMultiSelection.has(fieldConfig["renderer"]) ||
341
- builtIns.renderers.enumSingleSelection.has(fieldConfig["renderer"])) && (enums.get(fieldConfig["options"])) != fieldTypeDef.args[0]) {
342
- if (enums.has(fieldConfig["options"]))
343
- errors.push(`field ${fieldName} of form ${formName} references an enum api with type ${enums.get(fieldConfig["options"])} when ${fieldTypeDef.args[0]} was expected`);
344
- else
345
- errors.push(`field ${fieldName} of form ${formName} references a non-existing enum api`);
346
- } else if ((builtIns.renderers.streamMultiSelection.has(fieldConfig["renderer"]) ||
347
- builtIns.renderers.streamSingleSelection.has(fieldConfig["renderer"])) && (streams.get(fieldConfig["stream"])) != fieldTypeDef.args[0]) {
348
- if (streams.has(fieldConfig["stream"]))
349
- errors.push(`field ${fieldName} of form ${formName} references an api with type ${streams.get(fieldConfig["stream"])} when ${fieldTypeDef.args[0]} was expected`);
350
- else
351
- errors.push(`field ${fieldName} of form ${formName} references a non-existing stream api`);
352
- }
353
- }
294
+ if (!fieldTypeDef)
295
+ errors.push(`field ${fieldName} of form ${formName} has no corresponding type `);
354
296
  }
355
297
  })
356
298
  }
357
299
  })
358
300
 
301
+ const rendererMatchesType = (formName:string, fieldName:string) => (fieldTypeDef:Type, fieldConfig:any) => {
302
+ if (fieldTypeDef?.kind == "primitive") {
303
+ if (fieldTypeDef.value == "maybeBoolean") {
304
+ // alert(JSON.stringify(fieldConfig["renderer"]))
305
+ // alert(JSON.stringify(builtIns.renderers.MaybeBooleanViews))
306
+ if (!builtIns.renderers.maybeBoolean.has(fieldConfig["renderer"]))
307
+ errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
308
+ } else if (fieldTypeDef.value == "boolean") {
309
+ if (!builtIns.renderers.boolean.has(fieldConfig["renderer"]))
310
+ errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
311
+ } else if (fieldTypeDef.value == "number") {
312
+ if (!builtIns.renderers.number.has(fieldConfig["renderer"]))
313
+ errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
314
+ } else if (fieldTypeDef.value == "string") {
315
+ if (!builtIns.renderers.string.has(fieldConfig["renderer"]))
316
+ errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
317
+ } else if (fieldTypeDef.value == "Date") {
318
+ if (!builtIns.renderers.date.has(fieldConfig["renderer"])) {
319
+ alert(JSON.stringify(builtIns.renderers))
320
+ errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
321
+ }
322
+ } else {
323
+ errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
324
+ }
325
+ } else if (fieldTypeDef?.kind == "application") {
326
+ if (fieldTypeDef?.value == "SingleSelection") {
327
+ if (!builtIns.renderers.enumSingleSelection.has(fieldConfig["renderer"]) &&
328
+ !builtIns.renderers.streamSingleSelection.has(fieldConfig["renderer"]))
329
+ errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
330
+ } else if (fieldTypeDef?.value == "Multiselection") {
331
+ if (!builtIns.renderers.enumMultiSelection.has(fieldConfig["renderer"]) &&
332
+ !builtIns.renderers.streamMultiSelection.has(fieldConfig["renderer"]))
333
+ errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
334
+ } else if (fieldTypeDef?.value == "List") {
335
+ if (!builtIns.renderers.list.has(fieldConfig["renderer"]))
336
+ errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
337
+ } else if (fieldTypeDef?.value == "Map") {
338
+ if (!builtIns.renderers.map.has(fieldConfig["renderer"]))
339
+ errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
340
+ if ("keyRenderer" in fieldConfig != true || "valueRenderer" in fieldConfig != true)
341
+ errors.push(`field ${fieldName} of form ${formName} must have both a keyRenderer and a valueRenderer`);
342
+ if (fieldTypeDef.args.length != 2)
343
+ errors.push(`field ${fieldName} of form ${formName} should have exactly two type arguments`);
344
+ else {
345
+ const typeDefToType = (typeDef:any) : Type | undefined =>
346
+ "fun" in typeDef == false ? undefined
347
+ : "args" in typeDef == false ? undefined
348
+ : Array.isArray(typeDef.args) == false ? undefined
349
+ : Type.Default.application(typeDef.fun, typeDef.args)
350
+ const keyType:Type | undefined = typeof fieldTypeDef.args[0] == "string" ? Type.Operations.FromName(types, builtIns)(fieldTypeDef.args[0]) : typeDefToType(fieldTypeDef.args[0] as any)
351
+ const valueType:Type | undefined = typeof fieldTypeDef.args[1] == "string" ? Type.Operations.FromName(types, builtIns)(fieldTypeDef.args[1]) : typeDefToType(fieldTypeDef.args[1] as any)
352
+ if (!keyType) {
353
+ errors.push(`field ${fieldName} of form ${formName} references non-existing key type ${JSON.stringify(fieldTypeDef.args[0])}`);
354
+ } else if (!valueType) {
355
+ errors.push(`field ${fieldName} of form ${formName} references non-existing value type ${JSON.stringify(fieldTypeDef.args[1])}`);
356
+ } else {
357
+ rendererMatchesType(formName, fieldName)(keyType, fieldConfig.keyRenderer)
358
+ rendererMatchesType(formName, fieldName)(valueType, fieldConfig.valueRenderer)
359
+ }
360
+ }
361
+ }
362
+ if (fieldTypeDef.args.length < 1)
363
+ errors.push(`field ${fieldName} of form ${formName} should have one type argument}`);
364
+ else if (
365
+ (builtIns.renderers.list.has(fieldConfig["renderer"])) && "elementRenderer" in fieldConfig != true)
366
+ errors.push(`field ${fieldName} of form ${formName} is missing the 'elementRenderer' property`);
367
+ else if (
368
+ (builtIns.renderers.enumMultiSelection.has(fieldConfig["renderer"]) ||
369
+ builtIns.renderers.enumSingleSelection.has(fieldConfig["renderer"])) && "options" in fieldConfig != true)
370
+ errors.push(`field ${fieldName} of form ${formName} is missing the 'options' property`);
371
+ else if (
372
+ (builtIns.renderers.streamSingleSelection.has(fieldConfig["renderer"]) ||
373
+ builtIns.renderers.streamMultiSelection.has(fieldConfig["renderer"])) && "stream" in fieldConfig != true)
374
+ errors.push(`field ${fieldName} of form ${formName} is missing the 'stream' property`);
375
+ else if ((builtIns.renderers.enumMultiSelection.has(fieldConfig["renderer"]) ||
376
+ builtIns.renderers.enumSingleSelection.has(fieldConfig["renderer"])) && (enums.get(fieldConfig["options"])) != fieldTypeDef.args[0]) {
377
+ if (enums.has(fieldConfig["options"]))
378
+ errors.push(`field ${fieldName} of form ${formName} references an enum api with type ${enums.get(fieldConfig["options"])} when ${fieldTypeDef.args[0]} was expected`);
379
+ else
380
+ errors.push(`field ${fieldName} of form ${formName} references a non-existing enum api`);
381
+ } else if ((builtIns.renderers.streamMultiSelection.has(fieldConfig["renderer"]) ||
382
+ builtIns.renderers.streamSingleSelection.has(fieldConfig["renderer"])) && (streams.get(fieldConfig["stream"])) != fieldTypeDef.args[0]) {
383
+ if (streams.has(fieldConfig["stream"]))
384
+ errors.push(`field ${fieldName} of form ${formName} references an api with type ${streams.get(fieldConfig["stream"])} when ${fieldTypeDef.args[0]} was expected`);
385
+ else
386
+ errors.push(`field ${fieldName} of form ${formName} references a non-existing stream api`);
387
+ }
388
+ } else {
389
+ const formTypeDef = types.get(fieldTypeDef.name)
390
+ if (!formTypeDef) {
391
+ alert(JSON.stringify([fieldName, fieldTypeDef, fieldConfig]))
392
+ errors.push(`field ${fieldName} of form ${formName} references a non-existing type ${fieldTypeDef.name}`);
393
+ } else {
394
+ const form = forms.get(fieldConfig.renderer ?? "")
395
+ if (!form) {
396
+ errors.push(`field ${fieldName} of form ${formName} references non-existing form ${fieldConfig.renderer}`);
397
+ } else if (fieldTypeDef.name != form.typeDef.name) {
398
+ errors.push(`field ${fieldName} of form ${formName} expected renderer for ${fieldTypeDef.name} but instead found a renderer for ${form.typeDef.name}`);
399
+ }
400
+ }
401
+ }
402
+ }
403
+
359
404
  Object.keys(formsConfig["forms"]).forEach((formName: any) => {
360
405
  let formDef: FormDef = forms.get(formName)!
361
406
  const formTypeDef = types.get(formDef.type)
@@ -363,6 +408,8 @@ export const FormsConfig = {
363
408
  Object.keys(configFormDef["fields"]).forEach(fieldName => {
364
409
  const fieldConfig = configFormDef["fields"][fieldName]
365
410
  const fieldTypeDef = formTypeDef?.fields.get(fieldName);
411
+ if (fieldTypeDef)
412
+ rendererMatchesType(formName, fieldName)(fieldTypeDef, fieldConfig)
366
413
  if (fieldTypeDef && fieldTypeDef.kind == "application" && fieldTypeDef.value == "List" && (builtIns.renderers.list.has(fieldConfig["renderer"]))) {
367
414
  let elementRenderer = fieldConfig["elementRenderer"]
368
415
  let elementType = fieldTypeDef.args[0]
@@ -404,6 +451,10 @@ export const FormsConfig = {
404
451
  renderer: fieldConfig.renderer,
405
452
  label: fieldConfig.label ?? fieldName,
406
453
  elementRenderer: fieldConfig.elementRenderer,
454
+ mapRenderer:
455
+ fieldConfig.keyRenderer && fieldConfig.valueRenderer ?
456
+ { keyRenderer:fieldConfig.keyRenderer, valueRenderer:fieldConfig.valueRenderer }
457
+ : undefined,
407
458
  visible: BoolExpr.Default(fieldConfig.visible),
408
459
  disabled: fieldConfig.disabled != undefined ?
409
460
  BoolExpr.Default(fieldConfig.disabled)
@@ -1,5 +1,5 @@
1
1
  import { List, Map, OrderedMap, OrderedSet, Set } from "immutable";
2
- import { BoolExpr, Unit, PromiseRepo, Guid, LeafPredicatesEvaluators, Predicate, FormsConfig, BuiltIns, FormDef, Sum, BasicFun, Template, unit, EditFormState, EditFormTemplate, ApiErrors, CreateFormTemplate, EntityFormTemplate, SharedFormState, CreateFormState, Entity, EditFormContext, CreateFormContext, MappedEntityFormTemplate, Mapping, FormValidationResult, Synchronized, simpleUpdater, PrimitiveType, GenericType, ApiConverter, TypeName, ListFieldState, ListForm, TypeDefinition, ApiConverters, defaultValue, fromAPIRawValue, toAPIRawValue, EditFormForeignMutationsExpected } from "../../../../main";
2
+ import { BoolExpr, Unit, PromiseRepo, Guid, LeafPredicatesEvaluators, Predicate, FormsConfig, BuiltIns, FormDef, Sum, BasicFun, Template, unit, EditFormState, EditFormTemplate, ApiErrors, CreateFormTemplate, EntityFormTemplate, SharedFormState, CreateFormState, Entity, EditFormContext, CreateFormContext, MappedEntityFormTemplate, Mapping, FormValidationResult, Synchronized, simpleUpdater, PrimitiveType, GenericType, ApiConverter, TypeName, ListFieldState, ListForm, TypeDefinition, ApiConverters, defaultValue, fromAPIRawValue, toAPIRawValue, EditFormForeignMutationsExpected, MapFieldState, MapForm, Type, FieldConfig } from "../../../../main";
3
3
  import { Value } from "../../../value/state";
4
4
  import { CollectionReference } from "../collection/domains/reference/state";
5
5
  import { CollectionSelection } from "../collection/domains/selection/state";
@@ -24,7 +24,7 @@ const parseOptions = (leafPredicates: any, options: any) => {
24
24
  };
25
25
 
26
26
  export const FieldView = //<Context, FieldViews extends DefaultFieldViews, EnumFieldConfigs extends {}, EnumSources extends {}>() => <ViewType extends keyof FieldViews, ViewName extends keyof FieldViews[ViewType]>
27
- (fieldViews: any, viewType: any, viewName: any, fieldName: string, label: string, enumFieldConfigs: EnumOptionsSources, enumSources: any, leafPredicates: any): any => // FieldView<Context, FieldViews, ViewType, ViewName> =>
27
+ (fieldConfig:FieldConfig, fieldViews: any, viewType: any, viewName: any, fieldName: string, label: string, enumFieldConfigs: EnumOptionsSources, enumSources: any, leafPredicates: any): any => // FieldView<Context, FieldViews, ViewType, ViewName> =>
28
28
  {
29
29
  if (viewType == "maybeBoolean")
30
30
  return MaybeBooleanForm<any & FormLabel, Unit>()
@@ -50,13 +50,16 @@ export const FieldView = //<Context, FieldViews extends DefaultFieldViews, EnumF
50
50
  return EnumForm<any & FormLabel & BaseEnumContext<any, CollectionReference>, Unit, CollectionReference>()
51
51
  .withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
52
52
  .mapContext<any & EnumFormState<any & BaseEnumContext<any, CollectionReference>, CollectionReference> & Value<CollectionSelection<CollectionReference>>>(_ => ({
53
- ..._, label: label, getOptions: () => ((enumFieldConfigs as any)(((enumSources as any)[fieldName]))() as Promise<any>).then(options => parseOptions(leafPredicates, options))
53
+ ..._, label: label, getOptions: () => {
54
+ // console.log(fieldConfig, fieldViews, viewType, viewName, fieldName, label, enumFieldConfigs, enumSources, leafPredicates)
55
+ return ((enumFieldConfigs as any)((fieldConfig as any).options ?? fieldConfig.api.enumOptions)() as Promise<any>).then(options => parseOptions(leafPredicates, options))
56
+ }
54
57
  })) as any
55
58
  if (viewType == "enumMultiSelection")
56
59
  return EnumMultiselectForm<any & FormLabel & BaseEnumContext<any, CollectionReference>, Unit, CollectionReference>()
57
60
  .withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
58
61
  .mapContext<any & EnumFormState<any & BaseEnumContext<any, CollectionReference>, CollectionReference> & Value<OrderedMap<Guid, CollectionReference>>>(_ => ({
59
- ..._, label: label, getOptions: () => ((enumFieldConfigs as any)(((enumSources as any)[fieldName]))() as Promise<any>).then(options => parseOptions(leafPredicates, options))
62
+ ..._, label: label, getOptions: () => ((enumFieldConfigs as any)((fieldConfig as any).options ?? fieldConfig.api.enumOptions)() as Promise<any>).then(options => parseOptions(leafPredicates, options))
60
63
  })) as any
61
64
  if (viewType == "streamSingleSelection")
62
65
  return SearchableInfiniteStreamForm<CollectionReference, any & FormLabel, Unit>()
@@ -70,7 +73,7 @@ export const FieldView = //<Context, FieldViews extends DefaultFieldViews, EnumF
70
73
  }
71
74
 
72
75
  export const FieldFormState = //<Context, FieldViews extends DefaultFieldViews, InfiniteStreamSources extends {}, InfiniteStreamConfigs extends {}>() => <ViewType extends keyof FieldViews, ViewName extends keyof FieldViews[ViewType]>
73
- (fieldViews: any, viewType: any, viewName: any, fieldName: string, InfiniteStreamSources: any, infiniteStreamConfigs: any): any => {
76
+ (fieldConfig:FieldConfig, fieldViews: any, viewType: any, viewName: any, fieldName: string, InfiniteStreamSources: any, infiniteStreamConfigs: any): any => {
74
77
  if (viewType == "maybeBoolean" || viewType == "boolean" || viewType == "number" || viewType == "string")
75
78
  return SharedFormState.Default();
76
79
  if (viewType == "date")
@@ -79,12 +82,14 @@ export const FieldFormState = //<Context, FieldViews extends DefaultFieldViews,
79
82
  return ({ ...EnumFormState<any, any>().Default(), ...SharedFormState.Default() });
80
83
  if (viewType == "streamSingleSelection" || viewType == "streamMultiSelection") {
81
84
  return ({
82
- ...SearchableInfiniteStreamState<any>().Default("", (InfiniteStreamSources as any)((infiniteStreamConfigs as any)[fieldName])
85
+ ...SearchableInfiniteStreamState<any>().Default("", (InfiniteStreamSources as any)((fieldConfig as any).stream ?? fieldConfig.api.stream)
83
86
  ), ...SharedFormState.Default()
84
87
  });
85
88
  }
86
89
  if (viewType == "list")
87
90
  return ListFieldState<any, any>().Default(Map())
91
+ if (viewType == "map")
92
+ return MapFieldState<any, any, any, any>().Default(Map())
88
93
  return `error: the view for ${viewType as string}::${viewName as string} cannot be found when creating the corresponding field form state`;
89
94
  };
90
95
 
@@ -105,6 +110,8 @@ export const ParseForm = (
105
110
  otherForms: ParsedForms,
106
111
  fieldsViewsConfig: any,
107
112
  formFieldElementRenderers: any,
113
+ formFieldKeyRenderers: any,
114
+ formFieldValueRenderers: any,
108
115
  fieldsInfiniteStreamsConfig: any,
109
116
  fieldsOptionsConfig: any,
110
117
  InfiniteStreamSources: any,
@@ -112,7 +119,7 @@ export const ParseForm = (
112
119
  leafPredicates: any,
113
120
  visibleFieldsBoolExprs: any,
114
121
  disabledFieldsBoolExprs: any,
115
- defaultValue: BasicFun<TypeName, any>,
122
+ defaultValue: BasicFun<TypeName | Type, any>,
116
123
  type: TypeDefinition
117
124
  ): ParsedForm => {
118
125
  const fieldNameToViewCategory = (fieldName: string) => {
@@ -126,9 +133,10 @@ export const ParseForm = (
126
133
  }
127
134
  throw `cannot resolve view ${viewName} of field ${fieldName}`
128
135
  };
129
- const fieldNameToElementViewCategory = (fieldName: string) => {
136
+ const fieldNameToElementViewCategory = (formFieldKeyOrValueRenderers:any) => (fieldName: string) => {
130
137
  const fieldViewCategories = Object.keys(fieldViews)
131
- const viewName = (formFieldElementRenderers as any)[fieldName];
138
+ let viewName = (formFieldKeyOrValueRenderers as any)[fieldName];
139
+ viewName = viewName?.renderer ?? viewName
132
140
  for (const categoryName of fieldViewCategories) {
133
141
  const viewCategory = (fieldViews as any)[categoryName];
134
142
  if (viewName in viewCategory) {
@@ -141,16 +149,19 @@ export const ParseForm = (
141
149
  const initialFormState: any = SharedFormState.Default();
142
150
  Object.keys(fieldsViewsConfig).forEach(fieldName => {
143
151
  const viewName = (fieldsViewsConfig as any)[fieldName];
152
+ const fieldConfig = formDef.fields.get(fieldName)!
144
153
  initialFormState[fieldName] =
145
154
  otherForms.get(viewName)?.initialFormState ??
146
- FieldFormState(fieldViews, fieldNameToViewCategory(fieldName) as any, (fieldsViewsConfig as any)[fieldName], fieldName, InfiniteStreamSources, fieldsInfiniteStreamsConfig);
155
+ FieldFormState(fieldConfig, fieldViews, fieldNameToViewCategory(fieldName) as any, (fieldsViewsConfig as any)[fieldName], fieldName, InfiniteStreamSources, fieldsInfiniteStreamsConfig);
147
156
  if (typeof initialFormState[fieldName] == "string") {
148
157
  throw `cannot resolve initial state ${viewName} of field ${fieldName}`
149
158
  }
150
159
  });
151
160
  const formConfig: any = {};
152
- Object.keys(fieldsViewsConfig).forEach(fieldName => {
161
+ const fieldNames = Object.keys(fieldsViewsConfig)
162
+ fieldNames.forEach(fieldName => {
153
163
  const label = formDef.fields.get(fieldName)!.label
164
+ const fieldConfig = formDef.fields.get(fieldName)!
154
165
  const viewName = (fieldsViewsConfig as any)[fieldName];
155
166
  const otherForm = otherForms.get(viewName)
156
167
  if (otherForm != undefined) {
@@ -171,8 +182,8 @@ export const ParseForm = (
171
182
  ).withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
172
183
  .mapContext<any>(_ => ({ ..._, label: label }))
173
184
  } else { // the list argument is a primitive
174
- const elementForm = FieldView(fieldViews, fieldNameToElementViewCategory(fieldName) as any, elementRendererName, fieldName, label, EnumOptionsSources, fieldsOptionsConfig, leafPredicates)
175
- const initialFormState = FieldFormState(fieldViews, fieldNameToElementViewCategory(fieldName) as any, elementRendererName, fieldName, InfiniteStreamSources, fieldsInfiniteStreamsConfig);
185
+ const elementForm = FieldView(fieldConfig, fieldViews, fieldNameToElementViewCategory(formFieldElementRenderers)(fieldName) as any, elementRendererName, fieldName, label, EnumOptionsSources, fieldsOptionsConfig, leafPredicates)
186
+ const initialFormState = FieldFormState(fieldConfig, fieldViews, fieldNameToElementViewCategory(formFieldElementRenderers)(fieldName) as any, elementRendererName, fieldName, InfiniteStreamSources, fieldsInfiniteStreamsConfig);
176
187
  formConfig[fieldName] = ListForm<any, any, any & FormLabel, Unit>(
177
188
  { Default: () => initialFormState },
178
189
  { Default: () => initialElementValue },
@@ -181,7 +192,60 @@ export const ParseForm = (
181
192
  .mapContext<any>(_ => ({ ..._, label: label }))
182
193
  }
183
194
  } else {
184
- formConfig[fieldName] = FieldView(fieldViews, viewType, viewName, fieldName, label, EnumOptionsSources, fieldsOptionsConfig, leafPredicates);
195
+ if (viewType == "map") {
196
+ const field = type.fields.get(fieldName)!
197
+
198
+ const parseType = (t:any) : TypeName | Type | undefined => {
199
+ if (typeof t == "string") return t
200
+ if ("fun" in t && "args" in t && Array.isArray(t.args)) {
201
+ return { kind:"application", value:t.fun, args:t.args }
202
+ }
203
+ return undefined
204
+ }
205
+
206
+ const [keyType, valueType] = field.kind == "application" ? [parseType(field.args[0]), parseType(field.args[1])] : (() => {
207
+ throw `error processing field type ${JSON.stringify(field)} of ${fieldName}`
208
+ })()
209
+ if (!keyType || !valueType) {
210
+ throw `error processing field type ${JSON.stringify(field)} of ${fieldName}`
211
+ }
212
+ const initialKeyValue = defaultValue(keyType)
213
+ const initialValueValue = defaultValue(valueType)
214
+ const getFormAndInitialState = (elementRenderers:any, rendererName:any, fieldConfig:FieldConfig) => {
215
+ const formDef = otherForms.get(rendererName)
216
+ if (formDef != undefined) {
217
+ return [
218
+ formDef.form.withView(nestedContainerFormView),
219
+ formDef.initialFormState
220
+ ]
221
+ } else {
222
+ const categoryName = fieldNameToElementViewCategory(elementRenderers)(fieldName) as any
223
+ const form = FieldView(fieldConfig, fieldViews, categoryName, rendererName, fieldName, label, EnumOptionsSources, fieldsOptionsConfig, leafPredicates)
224
+ const initialFormState = FieldFormState(fieldConfig, fieldViews, categoryName, rendererName, fieldName, InfiniteStreamSources, fieldsInfiniteStreamsConfig);
225
+ return [
226
+ form,
227
+ initialFormState
228
+ ]
229
+ }
230
+ }
231
+ // alert(JSON.stringify([formFieldKeyValueRenderers[fieldName]]))
232
+ // alert(JSON.stringify([formFieldKeyValueRenderers]))
233
+ const keyRendererName = formFieldKeyRenderers[fieldName]
234
+ const valueRendererName = formFieldValueRenderers[fieldName]
235
+ const [keyForm,keyFormInitialState] = getFormAndInitialState(formFieldKeyRenderers, keyRendererName?.renderer ?? keyRendererName, keyRendererName as FieldConfig)
236
+ const [valueForm,valueFormInitialState] = getFormAndInitialState(formFieldValueRenderers, valueRendererName?.renderer ?? valueRendererName, keyRendererName as FieldConfig)
237
+ formConfig[fieldName] = MapForm<any, any, any, any, any & FormLabel, Unit>(
238
+ { Default: () => keyFormInitialState },
239
+ { Default: () => valueFormInitialState },
240
+ { Default: () => initialKeyValue },
241
+ { Default: () => initialValueValue },
242
+ keyForm,
243
+ valueForm
244
+ ).withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
245
+ .mapContext<any>(_ => ({ ..._, label: label }))
246
+ } else {
247
+ formConfig[fieldName] = FieldView(fieldConfig, fieldViews, viewType, viewName, fieldName, label, EnumOptionsSources, fieldsOptionsConfig, leafPredicates);
248
+ }
185
249
  }
186
250
  }
187
251
  if (typeof formConfig[fieldName] == "string") {
@@ -336,6 +400,8 @@ export const parseForms =
336
400
  const formFieldStreams = formConfig.fields.filter(field => field.api.stream != undefined).map(field => field.api.stream).toObject()
337
401
  const formFieldEnumOptions = formConfig.fields.filter(field => field.api.enumOptions != undefined).map(field => field.api.enumOptions).toObject()
338
402
  const formFieldElementRenderers = formConfig.fields.filter(field => field.elementRenderer != undefined).map(field => field.elementRenderer).toObject()
403
+ const formFieldKeyRenderers = formConfig.fields.filter(field => field.mapRenderer != undefined).map(field => field.mapRenderer?.keyRenderer).toObject()
404
+ const formFieldValueRenderers = formConfig.fields.filter(field => field.mapRenderer != undefined).map(field => field.mapRenderer?.valueRenderer).toObject()
339
405
 
340
406
  try {
341
407
  const parsedForm = ParseForm(
@@ -347,6 +413,8 @@ export const parseForms =
347
413
  parsedForms,
348
414
  formFieldRenderers,
349
415
  formFieldElementRenderers,
416
+ formFieldKeyRenderers,
417
+ formFieldValueRenderers,
350
418
  formFieldStreams,
351
419
  formFieldEnumOptions,
352
420
  infiniteStreamSources,
@@ -363,7 +431,7 @@ export const parseForms =
363
431
  }).mapContext<Unit>(_ => ({ ..._, disabledFields: parsedForm.disabledFields, visibleFields: parsedForm.visibleFields, layout: formConfig.tabs }))
364
432
  parsedForms = parsedForms.set(formName, { ...parsedForm, form })
365
433
  } catch (error: any) {
366
- errors.push(error)
434
+ errors.push(error.message ?? error)
367
435
  }
368
436
  })
369
437
 
@@ -0,0 +1,55 @@
1
+ import { List, Map } from "immutable";
2
+ import { simpleUpdater, Updater, SimpleCallback, Unit } from "../../../../../../main";
3
+ import { BasicFun } from "../../../../../fun/state";
4
+ import { Template, View } from "../../../../../template/state";
5
+ import { Value } from "../../../../../value/state";
6
+ import { FormLabel } from "../../../singleton/domains/form-label/state";
7
+ import { OnChange, SharedFormState } from "../../../singleton/state";
8
+
9
+
10
+ export type MapFieldState<K, V, KeyFormState, ValueFormState> =
11
+ SharedFormState &
12
+ {
13
+ elementFormStates: Map<number, { KeyFormState: KeyFormState, ValueFormState: ValueFormState }>
14
+ };
15
+ export const MapFieldState = <K, V, KeyFormState, ValueFormState>() => ({
16
+ Default: (
17
+ elementFormStates: MapFieldState<K, V, KeyFormState, ValueFormState>["elementFormStates"],
18
+ ): MapFieldState<K, V, KeyFormState, ValueFormState> => ({
19
+ ...SharedFormState.Default(),
20
+ elementFormStates
21
+ }),
22
+ Updaters: {
23
+ Core: {
24
+ ...simpleUpdater<MapFieldState<K, V, KeyFormState, ValueFormState>>()("elementFormStates"),
25
+ },
26
+ Template: {
27
+ }
28
+ }
29
+ });
30
+ export type MapFieldView<K, V, KeyFormState, ValueFormState, Context extends FormLabel, ForeignMutationsExpected> =
31
+ View<
32
+ Context & Value<List<[K, V]>> & MapFieldState<K, V, KeyFormState, ValueFormState>,
33
+ MapFieldState<K, V, KeyFormState, ValueFormState>,
34
+ ForeignMutationsExpected & {
35
+ onChange: OnChange<List<[K, V]>>;
36
+ add: SimpleCallback<Unit>;
37
+ remove: SimpleCallback<number>;
38
+ }, {
39
+ embeddedKeyTemplate: BasicFun<number, Template<
40
+ Context & Value<List<[K, V]>> & MapFieldState<K, V, KeyFormState, ValueFormState>,
41
+ MapFieldState<K, V, KeyFormState, ValueFormState>,
42
+ ForeignMutationsExpected & {
43
+ onChange: OnChange<List<[K, V]>>;
44
+ add: SimpleCallback<Unit>;
45
+ remove: SimpleCallback<number>;
46
+ }>>
47
+ embeddedValueTemplate: BasicFun<number, Template<
48
+ Context & Value<List<[K, V]>> & MapFieldState<K, V, KeyFormState, ValueFormState>,
49
+ MapFieldState<K, V, KeyFormState, ValueFormState>,
50
+ ForeignMutationsExpected & {
51
+ onChange: OnChange<List<[K, V]>>;
52
+ add: SimpleCallback<Unit>;
53
+ remove: SimpleCallback<number>;
54
+ }>>
55
+ }>;
@@ -0,0 +1,114 @@
1
+ import { List } from "immutable";
2
+ import { SimpleCallback, BasicFun, Unit, ValidateRunner, Updater, BasicUpdater, MapRepo, ListRepo } from "../../../../../../main";
3
+ import { Template } from "../../../../../template/state";
4
+ import { Value } from "../../../../../value/state";
5
+ import { FormLabel } from "../../../singleton/domains/form-label/state";
6
+ import { FieldValidation, FieldValidationWithPath, OnChange } from "../../../singleton/state";
7
+ import { MapFieldState, MapFieldView } from "./state";
8
+
9
+ export const MapForm = <K, V, KeyFormState, ValueFormState, Context extends FormLabel, ForeignMutationsExpected>(
10
+ KeyFormState: { Default: () => KeyFormState },
11
+ ValueFormState: { Default: () => ValueFormState },
12
+ Key: { Default: () => K },
13
+ Value: { Default: () => V },
14
+ keyTemplate: Template<
15
+ Context & Value<K> & KeyFormState,
16
+ KeyFormState,
17
+ ForeignMutationsExpected & {
18
+ onChange: OnChange<K>;
19
+ }>,
20
+ valueTemplate: Template<
21
+ Context & Value<V> & ValueFormState,
22
+ ValueFormState,
23
+ ForeignMutationsExpected & {
24
+ onChange: OnChange<V>;
25
+ }>,
26
+ validation?: BasicFun<List<[K, V]>, Promise<FieldValidation>>,
27
+ ) => {
28
+ const embeddedKeyTemplate = (elementIndex: number) =>
29
+ keyTemplate
30
+ .mapForeignMutations((_: ForeignMutationsExpected & {
31
+ onChange: OnChange<List<[K, V]>>;
32
+ add: SimpleCallback<Unit>;
33
+ remove: SimpleCallback<number>;
34
+ }): ForeignMutationsExpected & {
35
+ onChange: OnChange<K>;
36
+ } =>
37
+ ({
38
+ ..._,
39
+ onChange: (elementUpdater, path) => {
40
+ _.onChange(Updater((elements: List<[K, V]>) => elements.update(elementIndex, (_: [K, V] | undefined) => _ == undefined ? _ : [elementUpdater(_[0]), _[1]])), path)
41
+ },
42
+ add: (newElement: [K, V]) => { },
43
+ remove: (elementIndex: number) => { }
44
+ })
45
+ )
46
+ .mapContext((_: Context & Value<List<[K, V]>> & MapFieldState<K, V, KeyFormState, ValueFormState>): (Context & Value<K> & KeyFormState) | undefined => {
47
+ const element = _.value.get(elementIndex)
48
+ if (element == undefined) return undefined
49
+ const elementFormState = _.elementFormStates.get(elementIndex) || { KeyFormState: KeyFormState.Default(), ValueFormState: ValueFormState.Default() }
50
+ const elementContext: Context & Value<K> & KeyFormState = ({ ..._, ...elementFormState.KeyFormState, value: element[0] })
51
+ return elementContext
52
+ })
53
+ .mapState((_: BasicUpdater<KeyFormState>): Updater<MapFieldState<K, V, KeyFormState, ValueFormState>> =>
54
+ MapFieldState<K, V, KeyFormState, ValueFormState>().Updaters.Core.elementFormStates(
55
+ MapRepo.Updaters.upsert(elementIndex, () => ({ KeyFormState: KeyFormState.Default(), ValueFormState: ValueFormState.Default() }), current => ({ ...current, KeyFormState:_(current.KeyFormState) }))
56
+ ))
57
+ const embeddedValueTemplate = (elementIndex: number) =>
58
+ valueTemplate
59
+ .mapForeignMutations((_: ForeignMutationsExpected & {
60
+ onChange: OnChange<List<[K, V]>>;
61
+ add: SimpleCallback<Unit>;
62
+ remove: SimpleCallback<number>;
63
+ }): ForeignMutationsExpected & {
64
+ onChange: OnChange<V>;
65
+ } =>
66
+ ({
67
+ ..._,
68
+ onChange: (elementUpdater, path) => {
69
+ _.onChange(Updater((elements: List<[K, V]>) => elements.update(elementIndex, (_: [K, V] | undefined) => _ == undefined ? _ : [_[0], elementUpdater(_[1])])), path)
70
+ },
71
+ add: (newElement: [K, V]) => { },
72
+ remove: (elementIndex: number) => { }
73
+ })
74
+ )
75
+ .mapContext((_: Context & Value<List<[K, V]>> & MapFieldState<K, V, KeyFormState, ValueFormState>): (Context & Value<V> & ValueFormState) | undefined => {
76
+ const element = _.value.get(elementIndex)
77
+ if (element == undefined) return undefined
78
+ const elementFormState = _.elementFormStates.get(elementIndex) || { KeyFormState: KeyFormState.Default(), ValueFormState: ValueFormState.Default() }
79
+ const elementContext: Context & Value<V> & ValueFormState = ({ ..._, ...elementFormState.ValueFormState, value: element[1] })
80
+ return elementContext
81
+ })
82
+ .mapState((_: BasicUpdater<ValueFormState>): Updater<MapFieldState<K, V, KeyFormState, ValueFormState>> =>
83
+ MapFieldState<K, V, KeyFormState, ValueFormState>().Updaters.Core.elementFormStates(
84
+ MapRepo.Updaters.upsert(elementIndex, () => ({ KeyFormState: KeyFormState.Default(), ValueFormState: ValueFormState.Default() }), current => ({ ...current, ValueFormState: _(current.ValueFormState) }))
85
+ ))
86
+ return Template.Default<Context & Value<List<[K, V]>> & { disabled: boolean }, MapFieldState<K, V, KeyFormState, ValueFormState>, ForeignMutationsExpected & { onChange: OnChange<List<[K, V]>>; },
87
+ MapFieldView<K, V, KeyFormState, ValueFormState, Context, ForeignMutationsExpected>>(props => <>
88
+ <props.view {...props}
89
+ context={{
90
+ ...props.context,
91
+ }}
92
+ foreignMutations={{
93
+ ...props.foreignMutations,
94
+ add: (_) => {
95
+ props.foreignMutations.onChange(
96
+ ListRepo.Updaters.push([Key.Default(), Value.Default()]), List()
97
+ )
98
+ },
99
+ remove: (_) => {
100
+ props.foreignMutations.onChange(
101
+ ListRepo.Updaters.remove(_), List()
102
+ )
103
+ }
104
+ }}
105
+ embeddedKeyTemplate={embeddedKeyTemplate}
106
+ embeddedValueTemplate={embeddedValueTemplate}
107
+ />
108
+ </>
109
+ ).any([
110
+ ValidateRunner<Context & { disabled: boolean }, MapFieldState<K, V, KeyFormState, ValueFormState>, ForeignMutationsExpected, List<[K, V]>>(
111
+ validation ? _ => validation(_).then(FieldValidationWithPath.Default.fromFieldValidation) : undefined
112
+ ),
113
+ ]);
114
+ };