ballerina-core 1.0.0 → 1.0.2

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
@@ -87,8 +87,11 @@ export * from "./src/forms/domains/primitives/domains/searchable-infinite-stream
87
87
  export * from "./src/forms/domains/primitives/domains/searchable-infinite-stream/template"
88
88
  export * from "./src/forms/domains/primitives/domains/searchable-infinite-stream-multiselect/state"
89
89
  export * from "./src/forms/domains/primitives/domains/searchable-infinite-stream-multiselect/template"
90
+ export * from "./src/forms/domains/singleton/domains/mapping/state"
91
+ export * from "./src/forms/domains/singleton/domains/mapping/template"
90
92
  export * from "./src/forms/domains/parser/state"
91
93
  export * from "./src/forms/domains/parser/domains/validator/state"
94
+ export * from "./src/forms/domains/parser/domains/merger/state"
92
95
  export * from "./src/forms/domains/launcher/domains/edit/state"
93
96
  export * from "./src/forms/domains/launcher/domains/edit/template"
94
97
  export * from "./src/forms/domains/launcher/domains/edit/coroutines/runner"
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.0",
5
+ "version": "1.0.2",
6
6
  "main": "main.ts",
7
7
  "dependencies": {
8
8
  "immutable": "^5.0.0-beta.5",
package/readme.md ADDED
@@ -0,0 +1,122 @@
1
+ # Ballerina 🩰
2
+
3
+ Welcome to _ballerina_, the effortlessly elegant functional programming framework for frontend web development, with a particular but non-exclusive preference for React.
4
+
5
+
6
+ ## Quick start
7
+ Everything in Ballerina 🩰 is based on the separation of code into units called _domains_.
8
+
9
+ Create a Typescript/React project however you want (I like to use rspack but you can use whatever you prefer).
10
+
11
+ Head over to the sources, and create all the necessary files and directories for a new domain (this is just a best practice and it avoids merge conflicts in the long run):
12
+
13
+ ```
14
+ mkdir helloWorld
15
+ cd helloWorld
16
+ touch state.ts
17
+ touch template.tsx
18
+ mkdir coroutines
19
+ touch coroutines/runner.ts
20
+ ```
21
+
22
+ Let's define the state of our domain in `state.ts`:
23
+
24
+ ```ts
25
+ import { simpleUpdater } from "ballerina-core"
26
+
27
+ export type HelloWorldContext = { greeting:string }
28
+ export type HelloWorldState = { counter:number, toggle:boolean }
29
+ export const HelloWorldState = {
30
+ Default:() : HelloWorldState => ({
31
+ counter:0,
32
+ toggle:false
33
+ }),
34
+ Updaters:{
35
+ ...simpleUpdater<HelloWorldState>()("counter"),
36
+ ...simpleUpdater<HelloWorldState>()("toggle")
37
+ }
38
+ }
39
+ ```
40
+
41
+ Let's define a simple automation in `coroutine/runner.ts`:
42
+
43
+ ```ts
44
+ import { CoTypedFactory, replaceWith, Unit } from "ballerina-core"
45
+ import { HelloWorldContext, HelloWorldState } from "../state"
46
+ import { Range } from "immutable"
47
+
48
+ const Co = CoTypedFactory<HelloWorldContext, HelloWorldState>()
49
+ export const helloWorldRunner =
50
+ Co.Template<Unit>(
51
+ Co.Repeat(
52
+ Co.Seq([
53
+ Co.SetState(
54
+ HelloWorldState.Updaters.counter(replaceWith(0))
55
+ ),
56
+ Co.Wait(250),
57
+ Co.For(Range(0, 3))(
58
+ i => Co.Seq([
59
+ Co.SetState(
60
+ HelloWorldState.Updaters.counter(_ => _ + 1)
61
+ ),
62
+ Co.Wait(250)
63
+ ])
64
+ )
65
+ ])
66
+ )
67
+ )
68
+ ```
69
+
70
+ Let's put something ugly on the screen in `template.tsx`:
71
+
72
+ ```tsx
73
+ import { Template, Unit } from "ballerina-core";
74
+ import { HelloWorldContext, HelloWorldState } from "./state";
75
+ import { helloWorldRunner } from "./coroutines/runner";
76
+
77
+ export const HelloWorldTemplate =
78
+ Template.Default<HelloWorldContext & HelloWorldState, HelloWorldState, Unit>(props =>
79
+ <>
80
+ <p>{props.context.greeting}</p>
81
+ <p>Counter: {props.context.counter}</p>
82
+ <button
83
+ onClick={() => props.setState(HelloWorldState.Updaters.toggle(_ => !_))}>
84
+ Toggle {props.context.toggle ? "off" : "on"}
85
+ </button>
86
+ </>
87
+ ).any([
88
+ helloWorldRunner
89
+ ])
90
+ ```
91
+
92
+ Finally, let's render this as a top-level stateful domain from the React entry point (which file depends on the template you used):
93
+
94
+ ```tsx
95
+ import { useState } from "react";
96
+ import "./App.css";
97
+ import { HelloWorldState } from "./domains/helloWorld/state";
98
+ import { HelloWorldTemplate } from "./domains/helloWorld/template";
99
+ import { unit } from "ballerina-core";
100
+
101
+ export const App = (props: {}) => {
102
+ const [helloWorld, setHelloWorld] = useState(HelloWorldState.Default())
103
+ return <>
104
+ <HelloWorldTemplate
105
+ context={{
106
+ greeting:"Hello!",
107
+ ...helloWorld
108
+ }}
109
+ setState={setHelloWorld}
110
+ foreignMutations={unit}
111
+ view={unit}
112
+ />
113
+ </>
114
+ }
115
+ ```
116
+
117
+ Head over to the page and you will see an animated `counter` value that changes on its own as well as a button you can interact with.
118
+
119
+ We have barely scratched the surface of all you can do with Ballerina 🩰 though.
120
+
121
+
122
+ If you want to know more, head over to [the official git repo](https://github.com/giuseppemag/ballerina) and check out the samples and the official documentation.
@@ -0,0 +1,9 @@
1
+ import { FormValidationError } from "../validator/state"
2
+
3
+ export const FormsConfigMerger = {
4
+ Default:{
5
+ merge:(formsConfigs:any, errors:Array<FormValidationError>) : [any, Array<FormValidationError>] => {
6
+ return null!
7
+ }
8
+ }
9
+ }
@@ -1,5 +1,5 @@
1
1
  import { Set, Map, OrderedMap } from "immutable";
2
- import { BoolExpr, Sum } from "../../../../../../main";
2
+ import { BoolExpr, FormsConfigMerger, Sum } from "../../../../../../main";
3
3
 
4
4
 
5
5
  export type FieldName = string;
@@ -12,7 +12,7 @@ export type TypeDefinition = {
12
12
  export type Type = {
13
13
  kind: "lookup"; name: TypeName;
14
14
  } | {
15
- kind: "primitive"; value: "string" | "number" | "boolean" | "Date" | "CollectionReference";
15
+ kind: "primitive"; value: "string" | "number" | "maybeBoolean" | "boolean" | "Date" | "CollectionReference";
16
16
  } | {
17
17
  kind: "application"; value: TypeName; args: Array<TypeName>;
18
18
  };
@@ -65,6 +65,7 @@ export type BuiltIns = {
65
65
  generics: Set<string>;
66
66
  renderers: {
67
67
  BooleanViews: Set<string>;
68
+ MaybeBooleanViews: Set<string>;
68
69
  NumberViews: Set<string>;
69
70
  StringViews: Set<string>;
70
71
  DateViews: Set<string>;
@@ -80,6 +81,13 @@ export const FormsConfig = {
80
81
  Default: {
81
82
  validateAndParseAPIResponse: (builtIns: BuiltIns) => (formsConfig: any): FormValidationResult => {
82
83
  let errors: Array<FormValidationError> = [];
84
+ if (Array.isArray(formsConfig)) {
85
+ alert("formsConfig is an array!")
86
+ const merged = FormsConfigMerger.Default.merge(formsConfig, errors)
87
+ formsConfig = merged[0]
88
+ errors = merged[0]
89
+ }
90
+
83
91
  let types: Map<TypeName, TypeDefinition> = Map();
84
92
  if ("types" in formsConfig == false) {
85
93
  errors.push("the formsConfig does not contain a 'types' field");
@@ -133,9 +141,9 @@ export const FormsConfig = {
133
141
  if (fieldDef.kind == "primitive" && !builtIns.primitives.includes(fieldDef.value))
134
142
  errors.push(`field ${fieldName} of type ${typeName} is non-existent primitive type ${fieldDef.value}`);
135
143
  if (fieldDef.kind == "lookup" && !types.has(fieldDef.name))
136
- errors.push(`field ${fieldName} of type ${typeName} is non-existent defined type ${fieldDef.name}`);
144
+ errors.push(`field ${fieldName} of type ${typeName} is non-existent type ${fieldDef.name}`);
137
145
  if (fieldDef.kind == "application" && !builtIns.generics.includes(fieldDef.value))
138
- errors.push(`field ${fieldName} of type ${typeName} applies non-existent generic type ${fieldDef.value}`);
146
+ errors.push(`field ${fieldName} of type ${typeName} applies non-existent generic type ${fieldDef.value}`);
139
147
  if (fieldDef.kind == "application" && fieldDef.args.some(argType => !builtIns.primitives.includes(argType) && !types.has(argType)))
140
148
  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)))}`);
141
149
  if (fieldDef.kind == "application" && fieldDef.value == "SingleSelection") {
@@ -240,7 +248,12 @@ export const FormsConfig = {
240
248
  }
241
249
  const fieldTypeDef = formTypeDef?.fields.get(fieldName)
242
250
  if (fieldTypeDef?.kind == "primitive") {
243
- if (fieldTypeDef.value == "boolean") {
251
+ if (fieldTypeDef.value == "maybeBoolean") {
252
+ // alert(JSON.stringify(fieldConfig["renderer"]))
253
+ // alert(JSON.stringify(builtIns.renderers.MaybeBooleanViews))
254
+ if (!builtIns.renderers.MaybeBooleanViews.has(fieldConfig["renderer"]))
255
+ errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
256
+ } else if (fieldTypeDef.value == "boolean") {
244
257
  if (!builtIns.renderers.BooleanViews.has(fieldConfig["renderer"]))
245
258
  errors.push(`field ${fieldName} of form ${formName} references non-existing ${fieldTypeDef.value} 'renderer' ${fieldConfig["renderer"]}`);
246
259
  } else if (fieldTypeDef.value == "number") {
@@ -1,9 +1,9 @@
1
1
  import { Collection, 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 } 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 } 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";
6
- import { BooleanForm } from "../primitives/domains/boolean/template";
6
+ import { BooleanForm, MaybeBooleanForm } from "../primitives/domains/boolean/template";
7
7
  import { DateFormState } from "../primitives/domains/date/state";
8
8
  import { DateForm } from "../primitives/domains/date/template";
9
9
  import { EnumMultiselectForm } from "../primitives/domains/enum-multiselect/template";
@@ -25,47 +25,51 @@ const parseOptions = (leafPredicates: any, options: any) => {
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
27
  (fieldViews: any, viewType: any, viewName: any, fieldName: string, enumFieldConfigs: any, enumSources: any, leafPredicates: any): any => // FieldView<Context, FieldViews, ViewType, ViewName> =>
28
- viewType == "BooleanViews" ?
29
- BooleanForm<any & FormLabel, Unit>(_ => PromiseRepo.Default.mock(() => []))
28
+ viewType == "MaybeBooleanViews" ?
29
+ MaybeBooleanForm<any & FormLabel, Unit>(_ => PromiseRepo.Default.mock(() => []))
30
30
  .withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
31
31
  .mapContext<any & SharedFormState & Value<boolean>>(_ => ({ ..._, label: fieldName })) as any
32
- : viewType == "DateViews" ?
33
- DateForm<any & FormLabel, Unit>(_ => PromiseRepo.Default.mock(() => []))
32
+ : viewType == "BooleanViews" ?
33
+ BooleanForm<any & FormLabel, Unit>(_ => PromiseRepo.Default.mock(() => []))
34
34
  .withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
35
- .mapContext<any & DateFormState & Value<Date>>(_ => ({ ..._, label: fieldName })) as any
36
- : viewType == "NumberViews" ?
37
- NumberForm<any & FormLabel, Unit>(_ => PromiseRepo.Default.mock(() => []))
35
+ .mapContext<any & SharedFormState & Value<boolean>>(_ => ({ ..._, label: fieldName })) as any
36
+ : viewType == "DateViews" ?
37
+ DateForm<any & FormLabel, Unit>(_ => PromiseRepo.Default.mock(() => []))
38
38
  .withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
39
- .mapContext<any & SharedFormState & Value<number>>(_ => ({ ..._, label: fieldName })) as any
40
- : viewType == "StringViews" ?
41
- StringForm<any & FormLabel, Unit>(_ => PromiseRepo.Default.mock(() => []))
39
+ .mapContext<any & DateFormState & Value<Date>>(_ => ({ ..._, label: fieldName })) as any
40
+ : viewType == "NumberViews" ?
41
+ NumberForm<any & FormLabel, Unit>(_ => PromiseRepo.Default.mock(() => []))
42
42
  .withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
43
- .mapContext<any & SharedFormState & Value<string>>(_ => ({ ..._, label: fieldName })) as any
44
- : viewType == "EnumViews" ?
45
- EnumForm<any & FormLabel & BaseEnumContext<any, CollectionReference>, Unit, CollectionReference>(_ => PromiseRepo.Default.mock(() => []))
43
+ .mapContext<any & SharedFormState & Value<number>>(_ => ({ ..._, label: fieldName })) as any
44
+ : viewType == "StringViews" ?
45
+ StringForm<any & FormLabel, Unit>(_ => PromiseRepo.Default.mock(() => []))
46
46
  .withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
47
- .mapContext<any & EnumFormState<any & BaseEnumContext<any, CollectionReference>, CollectionReference> & Value<CollectionSelection<CollectionReference>>>(_ => ({
48
- ..._, label: fieldName, getOptions: () => ((enumFieldConfigs as any)((enumSources as any)[fieldName]) as Promise<any>).then(options => parseOptions(leafPredicates, options))
49
- })) as any
50
- : viewType == "EnumMultiselectViews" ?
51
- EnumMultiselectForm<any & FormLabel & BaseEnumContext<any, CollectionReference>, Unit, CollectionReference>(_ => PromiseRepo.Default.mock(() => []))
47
+ .mapContext<any & SharedFormState & Value<string>>(_ => ({ ..._, label: fieldName })) as any
48
+ : viewType == "EnumViews" ?
49
+ EnumForm<any & FormLabel & BaseEnumContext<any, CollectionReference>, Unit, CollectionReference>(_ => PromiseRepo.Default.mock(() => []))
52
50
  .withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
53
- .mapContext<any & EnumFormState<any & BaseEnumContext<any, CollectionReference>, CollectionReference> & Value<OrderedMap<Guid, CollectionReference>>>(_ => ({
51
+ .mapContext<any & EnumFormState<any & BaseEnumContext<any, CollectionReference>, CollectionReference> & Value<CollectionSelection<CollectionReference>>>(_ => ({
54
52
  ..._, label: fieldName, getOptions: () => ((enumFieldConfigs as any)((enumSources as any)[fieldName]) as Promise<any>).then(options => parseOptions(leafPredicates, options))
55
53
  })) as any
56
- : viewType == "InfiniteStreamViews" ?
57
- SearchableInfiniteStreamForm<CollectionReference, any & FormLabel, Unit>(_ => PromiseRepo.Default.mock(() => []))
54
+ : viewType == "EnumMultiselectViews" ?
55
+ EnumMultiselectForm<any & FormLabel & BaseEnumContext<any, CollectionReference>, Unit, CollectionReference>(_ => PromiseRepo.Default.mock(() => []))
58
56
  .withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
59
- .mapContext<any & SearchableInfiniteStreamState<CollectionReference> & Value<CollectionSelection<CollectionReference>>>(_ => ({ ..._, label: fieldName })) as any
60
- : viewType == "InfiniteStreamMultiselectViews" ?
61
- InfiniteMultiselectDropdownForm<CollectionReference, any & FormLabel, Unit>(_ => PromiseRepo.Default.mock(() => []))
57
+ .mapContext<any & EnumFormState<any & BaseEnumContext<any, CollectionReference>, CollectionReference> & Value<OrderedMap<Guid, CollectionReference>>>(_ => ({
58
+ ..._, label: fieldName, getOptions: () => ((enumFieldConfigs as any)((enumSources as any)[fieldName]) as Promise<any>).then(options => parseOptions(leafPredicates, options))
59
+ })) as any
60
+ : viewType == "InfiniteStreamViews" ?
61
+ SearchableInfiniteStreamForm<CollectionReference, any & FormLabel, Unit>(_ => PromiseRepo.Default.mock(() => []))
62
62
  .withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
63
- .mapContext<any & FormLabel & SharedFormState & SearchableInfiniteStreamState<CollectionReference> & Value<OrderedMap<Guid, CollectionReference>>>(_ => ({ ..._, label: fieldName })) as any
64
- : `error: the view for ${viewType as string}::${viewName as string} cannot be found`;
63
+ .mapContext<any & SearchableInfiniteStreamState<CollectionReference> & Value<CollectionSelection<CollectionReference>>>(_ => ({ ..._, label: fieldName })) as any
64
+ : viewType == "InfiniteStreamMultiselectViews" ?
65
+ InfiniteMultiselectDropdownForm<CollectionReference, any & FormLabel, Unit>(_ => PromiseRepo.Default.mock(() => []))
66
+ .withView(((fieldViews as any)[viewType] as any)[viewName]() as any)
67
+ .mapContext<any & FormLabel & SharedFormState & SearchableInfiniteStreamState<CollectionReference> & Value<OrderedMap<Guid, CollectionReference>>>(_ => ({ ..._, label: fieldName })) as any
68
+ : `error: the view for ${viewType as string}::${viewName as string} cannot be found`;
65
69
 
66
70
  export const FieldFormState = //<Context, FieldViews extends DefaultFieldViews, InfiniteStreamSources extends {}, InfiniteStreamConfigs extends {}>() => <ViewType extends keyof FieldViews, ViewName extends keyof FieldViews[ViewType]>
67
71
  (fieldViews: any, viewType: any, viewName: any, fieldName: string, InfiniteStreamSources: any, infiniteStreamConfigs: any): any => {
68
- if (viewType == "BooleanViews" || viewType == "NumberViews" || viewType == "StringViews")
72
+ if (viewType == "MaybeBooleanViews" || viewType == "BooleanViews" || viewType == "NumberViews" || viewType == "StringViews")
69
73
  return SharedFormState.Default();
70
74
  if (viewType == "DateViews")
71
75
  return DateFormState.Default("");
@@ -116,7 +120,6 @@ export const ParseForm = (
116
120
  otherForms.get(viewName)?.initialFormState ??
117
121
  FieldFormState(fieldViews, fieldNameToViewCategory(fieldName) as any, (fieldsViewsConfig as any)[fieldName], fieldName, InfiniteStreamSources, fieldsInfiniteStreamsConfig);
118
122
  if (typeof initialFormState[fieldName] == "string") {
119
- formConfig[fieldName] = (props: any) => <>Error: field {fieldName} with {viewName} could not be instantiated</>
120
123
  throw `cannot resolve initial state ${viewName} of field ${fieldName}`
121
124
  }
122
125
  });
@@ -151,10 +154,11 @@ export const parseVisibleFields = (
151
154
 
152
155
  export const builtInsFromFieldViews = (fieldViews: any): BuiltIns => {
153
156
  let builtins: BuiltIns = {
154
- "primitives": Set(["string", "number", "boolean", "Date", "CollectionReference"]),
157
+ "primitives": Set(["string", "number", "boolean", "maybeBoolean", "Date", "CollectionReference"]),
155
158
  "generics": Set(["SingleSelection", "Multiselection"]),
156
159
  "renderers": {
157
160
  "BooleanViews": Set(),
161
+ "MaybeBooleanViews": Set(),
158
162
  "DateViews": Set(),
159
163
  "EnumMultiselectViews": Set(),
160
164
  "EnumViews": Set(),
@@ -175,9 +179,38 @@ export const builtInsFromFieldViews = (fieldViews: any): BuiltIns => {
175
179
  return builtins
176
180
  }
177
181
 
182
+ export type EditLauncherContext<Entity, FormState, ExtraContext> =
183
+ Omit<
184
+ EditFormContext<Entity, FormState> &
185
+ EditFormState<Entity, FormState> & {
186
+ extraContext: ExtraContext,
187
+ containerFormView: any
188
+ }, "api" | "actualForm">
189
+
190
+ export type CreateLauncherContext<Entity, FormState, ExtraContext> =
191
+ Omit<
192
+ CreateFormContext<Entity, FormState> &
193
+ CreateFormState<Entity, FormState> & {
194
+ extraContext: ExtraContext,
195
+ containerFormView: any
196
+ submitButtonWrapper: any
197
+ }, "api" | "actualForm">
198
+
178
199
  export type ParsedLaunchers = {
179
- create:Map<string, { form: Template<any, any, Unit>, initialState:any, actualForm: EntityFormTemplate<any, any, any, any, any> }>,
180
- edit:Map<string, { form: Template<any, any, Unit>, initialState:any, actualForm: EntityFormTemplate<any, any, any, any, any> }>,
200
+ create: Map<string, <Entity, FormState, ExtraContext, Context extends CreateLauncherContext<Entity, FormState, ExtraContext>>() =>
201
+ {
202
+ form:
203
+ Template<CreateLauncherContext<Entity, FormState, ExtraContext> & CreateFormState<Entity, FormState>,
204
+ CreateFormState<Entity, FormState>, Unit>,
205
+ initialState: CreateFormState<Entity, FormState>
206
+ }>,
207
+ edit: Map<string, <Entity, FormState, ExtraContext, Context extends EditLauncherContext<Entity, FormState, ExtraContext>>() =>
208
+ {
209
+ form:
210
+ Template<EditLauncherContext<Entity, FormState, ExtraContext> & EditFormState<Entity, FormState>,
211
+ EditFormState<Entity, FormState>, Unit>,
212
+ initialState: EditFormState<Entity, FormState>
213
+ }>,
181
214
  }
182
215
  export type ParsedForms = Map<string, ParsedForm & { form: EntityFormTemplate<any, any, any, any, any> }>
183
216
  export type FormParsingErrors = Array<string>
@@ -186,10 +219,10 @@ export type StreamName = string
186
219
  export type InfiniteStreamSources = BasicFun<StreamName, SearchableInfiniteStreamState<CollectionReference>["getChunk"]>
187
220
  export type EntityName = string
188
221
  export type EntityApis = {
189
- create:BasicFun<EntityName, BasicFun<any, Promise<Unit>>>
190
- default:BasicFun<EntityName, BasicFun<Unit,Promise<any>>>
191
- update:BasicFun<EntityName, BasicFun<any,Promise<ApiErrors>>>
192
- get:BasicFun<EntityName, BasicFun<Guid,Promise<any>>>
222
+ create: BasicFun<EntityName, BasicFun<any, Promise<Unit>>>
223
+ default: BasicFun<EntityName, BasicFun<Unit, Promise<any>>>
224
+ update: BasicFun<EntityName, BasicFun<any, Promise<ApiErrors>>>
225
+ get: BasicFun<EntityName, BasicFun<Guid, Promise<any>>>
193
226
  }
194
227
  export type EnumName = string
195
228
  export type EnumOptionsSources = BasicFun<EnumName, Promise<Array<[CollectionReference, BoolExpr<Unit>]>>>
@@ -199,16 +232,16 @@ export const parseForms =
199
232
  fieldViews: any,
200
233
  infiniteStreamSources: InfiniteStreamSources,
201
234
  enumOptionsSources: EnumOptionsSources,
202
- entityApis:EntityApis,
235
+ entityApis: EntityApis,
203
236
  leafPredicates: LeafPredicates) =>
204
237
  (formsConfig: FormsConfig):
205
238
  FormParsingResult => {
206
239
  let errors: FormParsingErrors = []
207
240
  let seen = Set<string>()
208
241
  let formProcessingOrder = OrderedSet<string>()
209
- let parsedLaunchers:ParsedLaunchers = {
210
- create:Map(),
211
- edit:Map(),
242
+ let parsedLaunchers: ParsedLaunchers = {
243
+ create: Map(),
244
+ edit: Map(),
212
245
  }
213
246
  let parsedForms: ParsedForms = Map()
214
247
  const traverse = (formDef: FormDef) => {
@@ -268,16 +301,16 @@ export const parseForms =
268
301
  update: entityApis.update(launcher.api)
269
302
  }
270
303
  parsedLaunchers.edit = parsedLaunchers.edit.set(
271
- launcherName, {
272
- form:EditFormTemplate<any, any>().mapContext((parentContext:any) =>
273
- ({
274
- ...parentContext,
275
- api: api,
276
- actualForm:form.withView(parentContext.containerFormView).mapContext((_: any) => ({ ..._, rootValue: _.value, ...parentContext.extraContext }))
277
- })),
278
- initialState:EditFormState<any, any>().Default(initialState),
279
- actualForm:form,
280
- }
304
+ launcherName,
305
+ <Entity, FormState, ExtraContext, Context extends EditLauncherContext<Entity, FormState, ExtraContext>>() => ({
306
+ form: EditFormTemplate<Entity, FormState>().mapContext((parentContext: Context) =>
307
+ ({
308
+ ...parentContext,
309
+ api: api,
310
+ actualForm: form.withView(parentContext.containerFormView).mapContext((_: any) => ({ ..._, rootValue: _.value, ...parentContext.extraContext }))
311
+ }) as any),
312
+ initialState: EditFormState<Entity, FormState>().Default(initialState),
313
+ })
281
314
  )
282
315
  })
283
316
 
@@ -290,22 +323,22 @@ export const parseForms =
290
323
  default: entityApis.default(launcher.api)
291
324
  }
292
325
  parsedLaunchers.create = parsedLaunchers.create.set(
293
- launcherName, {
294
- form:CreateFormTemplate<any, any>().mapContext((parentContext:any) =>
295
- ({
296
- ...parentContext,
297
- api: api,
298
- actualForm:form.withView(parentContext.containerFormView).mapContext((_: any) => ({ ..._, rootValue: _.value, ...parentContext.extraContext }))
299
- }))
326
+ launcherName,
327
+ <Entity, FormState, ExtraContext, Context extends CreateLauncherContext<Entity, FormState, ExtraContext>>() => ({
328
+ form: CreateFormTemplate<Entity, FormState>().mapContext((parentContext: Context) =>
329
+ ({
330
+ ...parentContext,
331
+ api: api,
332
+ actualForm: form.withView(parentContext.containerFormView).mapContext((_: any) => ({ ..._, rootValue: _.value, ...parentContext.extraContext }))
333
+ }) as any)
300
334
  .withViewFromProps(props => props.context.submitButtonWrapper)
301
335
  .mapForeignMutationsFromProps(props => props.foreignMutations as any),
302
- initialState:EditFormState<any, any>().Default(initialState),
303
- actualForm:form,
304
- }
336
+ initialState: CreateFormState<any, any>().Default(initialState),
337
+ actualForm: form,
338
+ })
305
339
  )
306
340
  })
307
341
 
308
342
  if (errors.length > 0) return Sum.Default.right(errors)
309
343
  return Sum.Default.left(parsedLaunchers)
310
344
  }
311
-
@@ -11,3 +11,10 @@ export type BooleanView<Context extends FormLabel, ForeignMutationsExpected> =
11
11
  SharedFormState,
12
12
  ForeignMutationsExpected & { onChange: OnChange<boolean>; setNewValue: SimpleCallback<boolean> }
13
13
  >;
14
+
15
+ export type MaybeBooleanView<Context extends FormLabel, ForeignMutationsExpected> =
16
+ View<
17
+ Context & Value<boolean | undefined> & SharedFormState,
18
+ SharedFormState,
19
+ ForeignMutationsExpected & { onChange: OnChange<boolean | undefined>; setNewValue: SimpleCallback<boolean | undefined> }
20
+ >;
@@ -1,5 +1,5 @@
1
1
  import { List } from "immutable";
2
- import { BasicFun, BooleanView, CoTypedFactory, Debounce, Debounced, replaceWith, Synchronize, Unit, ValidateRunner } from "../../../../../../main";
2
+ import { BasicFun, BooleanView, CoTypedFactory, Debounce, Debounced, MaybeBooleanView, replaceWith, Synchronize, Unit, ValidateRunner } from "../../../../../../main";
3
3
  import { Template } from "../../../../../template/state";
4
4
  import { Value } from "../../../../../value/state";
5
5
  import { FormLabel } from "../../../singleton/domains/form-label/state";
@@ -22,3 +22,20 @@ export const BooleanForm = <Context extends FormLabel, ForeignMutationsExpected>
22
22
  ),
23
23
  ]);
24
24
  }
25
+
26
+ export const MaybeBooleanForm = <Context extends FormLabel, ForeignMutationsExpected>(
27
+ validation: BasicFun<boolean | undefined, Promise<FieldValidation>>
28
+ ) => {
29
+ return Template.Default<Context & Value<boolean | undefined>, SharedFormState, ForeignMutationsExpected & { onChange: OnChange<boolean | undefined>; }, MaybeBooleanView<Context, ForeignMutationsExpected>>(props => <>
30
+ <props.view {...props}
31
+ foreignMutations={{
32
+ ...props.foreignMutations,
33
+ setNewValue: (_) => props.foreignMutations.onChange(replaceWith(_), List())
34
+ }} />
35
+ </>
36
+ ).any([
37
+ ValidateRunner<Context, SharedFormState, ForeignMutationsExpected, boolean | undefined>(
38
+ _ => validation(_).then(FieldValidationWithPath.Default.fromFieldValidation)
39
+ ),
40
+ ]);
41
+ }
@@ -0,0 +1,113 @@
1
+ import { OrderedMap } from "immutable";
2
+ import { Guid, Unit } from "../../../../../../main";
3
+ import { BasicFun } from "../../../../../fun/state";
4
+ import { CollectionSelection } from "../../../collection/domains/selection/state";
5
+
6
+
7
+ export type UntypedPath = Array<string | number | symbol>;
8
+ export type MappingPaths<Entity> = {
9
+ [field in keyof Entity]: Entity[field] extends (string | boolean | number | undefined | Date | CollectionSelection<infer _> | OrderedMap<Guid, infer _>) ? UntypedPath : MappingPaths<Entity[field]>;
10
+ };
11
+
12
+ export type MappingBuilder<Source, Target, mappedTargetFields extends keyof Target> =
13
+ (<field extends Exclude<keyof Target, mappedTargetFields>>(field: field) => Target[field] extends (string | boolean | number | undefined | Date | CollectionSelection<infer _> | OrderedMap<Guid, infer _>) ? BasicFun<
14
+ BasicFun<
15
+ PathBuilder<Source>, PathBuilder<Target[field]>
16
+ >, MappingBuilder<Source, Target, mappedTargetFields | field>> : BasicFun<
17
+ MappingBuilder<Source, Target[field], keyof Target[field]>, MappingBuilder<Source, Target, mappedTargetFields | field>>) & { kind: "mappingBuilder"; paths: MappingPaths<Pick<Target, mappedTargetFields>>; };
18
+
19
+ export type PathBuilder<Entity> =
20
+ (<field extends keyof Entity>(field: field) => PathBuilder<Entity[field]>) & {
21
+ path: UntypedPath;
22
+ };
23
+
24
+ export const PathBuilder = {
25
+ Default: <Entity,>(path: UntypedPath): PathBuilder<Entity> => Object.assign(
26
+ <field extends keyof Entity>(field: field): PathBuilder<Entity[field]> => PathBuilder.Default<Entity[field]>([...path, field]),
27
+ {
28
+ path: path,
29
+ kind: "pathBuilder"
30
+ }
31
+ )
32
+ };
33
+
34
+ export const MappingBuilder = {
35
+ Default: <Source, Target, mappedTargetFields extends keyof Target = never>(paths: MappingPaths<Pick<Target, mappedTargetFields>>): MappingBuilder<Source, Target, mappedTargetFields> => Object.assign(
36
+ <field extends keyof Target>(field: field): any => ((_: MappingBuilder<Source, Unit, never> | BasicFun<
37
+ PathBuilder<Source>, PathBuilder<Target[field]>>
38
+ ): MappingBuilder<Source, Target, mappedTargetFields | field> => {
39
+ if ("kind" in _ == false || _.kind != "mappingBuilder") {
40
+ const fieldPathBuilder = _ as BasicFun<PathBuilder<Source>, PathBuilder<Target[field]>>;
41
+ const pathToField = fieldPathBuilder(PathBuilder.Default<Source>([])).path;
42
+ const extendedPaths: MappingPaths<Pick<Target, mappedTargetFields | field>> = {
43
+ ...paths,
44
+ [field]: pathToField
45
+ } as any;
46
+ const remainingMappingBuilder = MappingBuilder.Default<Source, Target, mappedTargetFields | field>(extendedPaths);
47
+ return remainingMappingBuilder;
48
+ }
49
+ const fieldEntityMappingBuilder = _ as MappingBuilder<Source, Unit, never>;
50
+ const extendedPaths: MappingPaths<Pick<Target, mappedTargetFields | field>> = {
51
+ ...paths,
52
+ [field]: fieldEntityMappingBuilder.paths
53
+ } as any;
54
+ const remainingMappingBuilder = MappingBuilder.Default<Source, Target, mappedTargetFields | field>(extendedPaths);
55
+ return remainingMappingBuilder;
56
+ }),
57
+ {
58
+ paths: paths,
59
+ kind: "mappingBuilder" as const
60
+ }
61
+ )
62
+ };
63
+
64
+ export type Mapping<Source, Target> = {
65
+ from: BasicFun<Source, Target>;
66
+ to: BasicFun<[Source, Target], Source>;
67
+ pathFrom: BasicFun<Array<string>, Array<string>>;
68
+ };
69
+ const dynamicLookup = (e: any, path: UntypedPath): any => path.length <= 0 ? e : dynamicLookup(e[path[0]], path.slice(1));
70
+ const dynamicAssignment = (e: any, v: any, path: UntypedPath): any => {
71
+ if (path.length <= 0) return v;
72
+ if (path.length <= 1) {
73
+ return { ...e, [path[0]]: v };
74
+ }
75
+ return { ...e, [path[0]]: dynamicAssignment(e[path[0]], v, path.slice(1)) };
76
+ };
77
+ export const Mapping = {
78
+ Default: <Source, Target>(completedBuilder: MappingBuilder<Source, Target, keyof Target>): Mapping<Source, Target> => ({
79
+ from: s => {
80
+ const traversePaths = (paths: MappingPaths<any>) => {
81
+ const result_t = {} as any;
82
+ Object.keys(paths).forEach(_ => {
83
+ const field_t = _ as keyof Target;
84
+ if (Array.isArray(paths[field_t])) {
85
+ const pathToFieldT = paths[field_t] as UntypedPath;
86
+ result_t[field_t] = dynamicLookup(s, pathToFieldT);
87
+ } else {
88
+ result_t[field_t] = traversePaths(paths[field_t]);
89
+ }
90
+ });
91
+ return result_t;
92
+ };
93
+ return traversePaths(completedBuilder.paths);
94
+ },
95
+ to: ([s, t]) => {
96
+ let result_s = s;
97
+ const traversePaths = (t: any, paths: MappingPaths<any>) => {
98
+ Object.keys(paths).forEach(_ => {
99
+ const field_t = _ as keyof Target;
100
+ if (Array.isArray(paths[field_t])) {
101
+ result_s = dynamicAssignment(result_s, t[field_t], paths[field_t]);
102
+ } else {
103
+ traversePaths(t[field_t], paths[field_t]);
104
+ }
105
+ });
106
+ };
107
+ traversePaths(t, completedBuilder.paths);
108
+ return result_s;
109
+ },
110
+ pathFrom: (pathInTarget: Array<string>): Array<string> => dynamicLookup(completedBuilder.paths, pathInTarget),
111
+ }),
112
+ };
113
+
@@ -0,0 +1,30 @@
1
+ import { List } from "immutable";
2
+ import { Template } from "../../../../../template/state";
3
+ import { Value } from "../../../../../value/state";
4
+ import { EntityFormContext, EntityFormState, EntityFormForeignMutationsExpected, OnChange, EntityFormView, EntityFormTemplate } from "../../state";
5
+ import { Mapping } from "./state";
6
+
7
+
8
+ export type MappedEntityFormTemplate<SourceEntity, Entity, FieldStates, ExtraContext, ExtraForeignMutationsExpected> = Template<
9
+ Omit<EntityFormContext<Entity, (keyof Entity) & (keyof FieldStates), FieldStates, ExtraContext, ExtraForeignMutationsExpected>, "value"> & Value<SourceEntity>, EntityFormState<Entity, (keyof Entity) & (keyof FieldStates), FieldStates, ExtraContext, ExtraForeignMutationsExpected>, Omit<EntityFormForeignMutationsExpected<Entity, (keyof Entity) & (keyof FieldStates), FieldStates, ExtraContext, ExtraForeignMutationsExpected>, "onChange"> & { onChange: OnChange<SourceEntity>; }, EntityFormView<Entity, (keyof Entity) & (keyof FieldStates), FieldStates, ExtraContext, ExtraForeignMutationsExpected>
10
+ >;
11
+
12
+ export const MappedEntityFormTemplate = <SourceEntity, Entity, FieldStates, ExtraContext, ExtraForeignMutationsExpected>(
13
+ mapping: Mapping<SourceEntity, Entity>,
14
+ form: EntityFormTemplate<
15
+ Entity, (keyof Entity) & (keyof FieldStates), FieldStates, ExtraContext, ExtraForeignMutationsExpected>
16
+ ): MappedEntityFormTemplate<
17
+ SourceEntity, Entity, FieldStates, ExtraContext, ExtraForeignMutationsExpected> => form
18
+ .mapContext((_: Omit<EntityFormContext<Entity, (keyof Entity) & (keyof FieldStates), FieldStates, ExtraContext, ExtraForeignMutationsExpected>, "value"> & Value<SourceEntity>) => ({ ..._, value: mapping.from(_.value) }) as any)
19
+ .mapForeignMutations((_: any) => ({
20
+ ..._,
21
+ onChange: (u, path) => {
22
+ _.onChange(
23
+ (current: any) => mapping.to([
24
+ current,
25
+ u(mapping.from(current))
26
+ ]),
27
+ List(mapping.pathFrom(path.toArray()))
28
+ );
29
+ }
30
+ }));
@@ -35,7 +35,7 @@ export const Form = <Entity, FieldStates, Context, ForeignMutationsExpected>() =
35
35
  props.foreignMutations.onChange((current: Entity): Entity => ({
36
36
  ...current,
37
37
  [field]: _(current[field])
38
- }), path.push(field as string)),
38
+ }), path.unshift(field as string)),
39
39
  0)
40
40
  }
41
41
  }))