@sealcode/sealgen 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/.nyc_output/1c5dff80-7861-427a-b1bb-d0686211f098.json +1 -0
  2. package/.nyc_output/a38a0d08-1f1f-4973-b5e2-efc434c1593d.json +1 -0
  3. package/.nyc_output/processinfo/1c5dff80-7861-427a-b1bb-d0686211f098.json +1 -0
  4. package/.nyc_output/processinfo/a38a0d08-1f1f-4973-b5e2-efc434c1593d.json +1 -0
  5. package/.nyc_output/processinfo/index.json +1 -0
  6. package/.xunit +3 -0
  7. package/@types/forms/controls/field-group.d.ts +17 -0
  8. package/@types/forms/controls/form-control.d.ts +1 -1
  9. package/@types/forms/controls/form-field-control.d.ts +1 -1
  10. package/@types/forms/controls/simple-input.d.ts +2 -1
  11. package/@types/forms/form.d.ts +19 -6
  12. package/@types/forms/multiform.d.ts +9 -11
  13. package/@types/index.d.ts +1 -0
  14. package/@types/page/mountable-with-fields.d.ts +32 -0
  15. package/@types/page/mountable.d.ts +2 -27
  16. package/@types/page/page.d.ts +2 -2
  17. package/@types/templates/multiform.d.ts +1 -0
  18. package/@types/templates/redirect.d.ts +1 -0
  19. package/@types/utils/input-wrapper.d.ts +4 -0
  20. package/coverage/clover.xml +79 -0
  21. package/lib/add-route.js +27 -8
  22. package/lib/add-route.js.map +1 -1
  23. package/lib/forms/controls/checkboxed-list-input.js +12 -9
  24. package/lib/forms/controls/checkboxed-list-input.js.map +1 -1
  25. package/lib/forms/controls/dropdown.js +14 -12
  26. package/lib/forms/controls/dropdown.js.map +1 -1
  27. package/lib/forms/controls/field-group.js +26 -0
  28. package/lib/forms/controls/field-group.js.map +1 -0
  29. package/lib/forms/controls/form-control.js.map +1 -1
  30. package/lib/forms/controls/form-field-control.js.map +1 -1
  31. package/lib/forms/controls/simple-input.js +6 -8
  32. package/lib/forms/controls/simple-input.js.map +1 -1
  33. package/lib/forms/controls/textarea.js +13 -15
  34. package/lib/forms/controls/textarea.js.map +1 -1
  35. package/lib/forms/form.js +95 -57
  36. package/lib/forms/form.js.map +1 -1
  37. package/lib/forms/multiform.js +52 -62
  38. package/lib/forms/multiform.js.map +1 -1
  39. package/lib/generate-routes.js +7 -3
  40. package/lib/generate-routes.js.map +1 -1
  41. package/lib/index.js +1 -0
  42. package/lib/index.js.map +1 -1
  43. package/lib/page/list.js +1 -1
  44. package/lib/page/list.js.map +1 -1
  45. package/lib/page/mountable-with-fields.js +63 -0
  46. package/lib/page/mountable-with-fields.js.map +1 -0
  47. package/lib/page/mountable.js +2 -49
  48. package/lib/page/mountable.js.map +1 -1
  49. package/lib/page/page.js +2 -2
  50. package/lib/page/page.js.map +1 -1
  51. package/lib/templates/multiform.js +59 -0
  52. package/lib/templates/multiform.js.map +1 -0
  53. package/lib/templates/redirect.js +27 -0
  54. package/lib/templates/redirect.js.map +1 -0
  55. package/lib/tests/manual/multiform.js +14 -5
  56. package/lib/tests/manual/multiform.js.map +1 -1
  57. package/lib/utils/input-wrapper.js +20 -0
  58. package/lib/utils/input-wrapper.js.map +1 -0
  59. package/package.json +4 -3
  60. package/src/add-route.ts +30 -11
  61. package/src/forms/controls/checkboxed-list-input.ts +17 -10
  62. package/src/forms/controls/dropdown.ts +28 -23
  63. package/src/forms/controls/form-control.ts +3 -1
  64. package/src/forms/controls/form-field-control.ts +4 -1
  65. package/src/forms/controls/simple-input.ts +19 -12
  66. package/src/forms/controls/textarea.ts +15 -14
  67. package/src/forms/form.ts +122 -76
  68. package/src/forms/multiform.ts +211 -0
  69. package/src/generate-routes.ts +7 -3
  70. package/src/index.ts +1 -0
  71. package/src/page/list.ts +1 -1
  72. package/src/page/mountable-with-fields.ts +112 -0
  73. package/src/page/mountable.ts +4 -96
  74. package/src/page/page.ts +7 -3
  75. package/src/templates/multiform.ts +58 -0
  76. package/src/templates/redirect.ts +22 -0
  77. package/src/tests/manual/multiform.ts +106 -0
  78. package/src/utils/input-wrapper.ts +18 -0
@@ -0,0 +1,112 @@
1
+ import { Context } from "koa";
2
+ import { FlatTemplatable, tempstream } from "tempstream";
3
+ import { FormControl } from "../forms/controls/form-control";
4
+ import { FormField } from "../forms/fields/field";
5
+ import { FormData, FormDataValue, FormMessage } from "../forms/form";
6
+ import { Mountable } from "./mountable";
7
+
8
+ export type PageErrorMessage = { type: "access" | "internal"; message: string };
9
+
10
+ export type Fields = Record<string, FormField<unknown>>;
11
+
12
+ export abstract class MountableWithFields<
13
+ F extends Fields = Fields
14
+ > extends Mountable {
15
+ fields: F;
16
+ field_names_prefix = ""; // useful for multiform, where many forms are merged into one and field assignment is made using the prefix
17
+ form_id = ""; // all fields within this mountable will be tied to form of this id
18
+ abstract controls: FormControl[];
19
+
20
+ constructor() {
21
+ super();
22
+ if (!this.fields) this.fields = {} as F;
23
+ }
24
+
25
+ init(): void {
26
+ for (const [name, field] of Object.entries(this.fields)) {
27
+ field.init(name);
28
+ }
29
+ }
30
+
31
+ // this one is meant to be overwritten
32
+ async validateValues(
33
+ ctx: Context,
34
+ data: Record<string, FormDataValue>
35
+ ): Promise<{ valid: boolean; error: string }> {
36
+ return {
37
+ valid: true,
38
+ error: "",
39
+ };
40
+ }
41
+
42
+ async validate(
43
+ ctx: Context,
44
+ values: Record<string, FormDataValue>
45
+ ): Promise<{
46
+ valid: boolean;
47
+ field_errors: Partial<Record<keyof Fields, string>>;
48
+ form_messages: FormMessage[];
49
+ }> {
50
+ const field_errors = {} as Record<keyof Fields, string>;
51
+ let valid = true;
52
+ const form_messages = [] as FormMessage[];
53
+ await Promise.all(
54
+ Object.entries(values)
55
+ .filter(([fieldname]) => fieldname in this.fields)
56
+ .map(async ([key]: [keyof F, FormDataValue]) => {
57
+ const field = this.fields[key];
58
+ const { valid: fieldvalid, message: fieldmessage } =
59
+ await field.getValue(ctx, values);
60
+ if (!fieldvalid) {
61
+ valid = false;
62
+ field_errors[field.name] = fieldmessage;
63
+ }
64
+ })
65
+ );
66
+ const formValidationResult = await this.validateValues(ctx, values);
67
+ if (!formValidationResult.valid) {
68
+ form_messages.push({
69
+ type: "error",
70
+ text: formValidationResult.error,
71
+ });
72
+ valid = false;
73
+ }
74
+ return { valid, field_errors, form_messages };
75
+ }
76
+
77
+ public renderControls(
78
+ ctx: Context,
79
+ data: FormData<keyof Fields>
80
+ ): FlatTemplatable {
81
+ return tempstream/* HTML */ `${this.controls.map((control) =>
82
+ control.render(
83
+ ctx,
84
+ data.raw_values,
85
+ data.messages,
86
+ this.field_names_prefix,
87
+ this.form_id
88
+ )
89
+ )}`;
90
+ }
91
+
92
+ async renderError(
93
+ _: Context,
94
+ error: PageErrorMessage
95
+ ): Promise<FlatTemplatable> {
96
+ return error.message;
97
+ }
98
+
99
+ public renderMessages(
100
+ _: Context,
101
+ data: FormData<keyof Fields>
102
+ ): FlatTemplatable {
103
+ return tempstream/* HTML */ `<div class="form-messages">
104
+ ${data.messages.map(
105
+ (message) =>
106
+ `<div class="form-message form-message--${message.type}">${message.text}</div>`
107
+ )}
108
+ </div>`;
109
+ }
110
+
111
+ abstract extractRawValues(ctx: Context): Record<string, FormDataValue>;
112
+ }
@@ -1,105 +1,13 @@
1
1
  import Router from "@koa/router";
2
- import { FormControl } from "../forms/controls/form-control";
3
- import { Context } from "koa";
4
- import { FlatTemplatable, tempstream } from "tempstream";
5
- import { FormField } from "../forms/fields/field";
6
- import { FormData, FormDataValue, FormMessage } from "../forms/form";
7
-
8
- export type PageErrorMessage = { type: "access" | "internal"; message: string };
9
-
10
- export type Fields = Record<string, FormField<unknown>>;
11
2
 
12
- export abstract class Mountable<F extends Fields = Fields> {
13
- fields: F;
14
-
15
- constructor() {
16
- if (!this.fields) this.fields = {} as F;
17
- }
3
+ import { Context } from "koa";
18
4
 
19
- abstract controls: FormControl[];
5
+ export abstract class Mountable {
6
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
7
+ init(): void {}
20
8
  abstract mount(router: Router, path: string): void;
21
9
 
22
10
  abstract canAccess(
23
11
  ctx: Context
24
12
  ): Promise<{ canAccess: boolean; message: string }>;
25
-
26
- abstract renderError(
27
- ctx: Context,
28
- error: PageErrorMessage
29
- ): Promise<FlatTemplatable>;
30
-
31
- abstract extractRawValues(ctx: Context): Record<string, FormDataValue>;
32
-
33
- // this one is meant to be overwritten
34
- async validateValues(
35
- ctx: Context,
36
- data: Record<string, FormDataValue>
37
- ): Promise<{ valid: boolean; error: string }> {
38
- return {
39
- valid: true,
40
- error: "",
41
- };
42
- }
43
-
44
- protected async validate(
45
- ctx: Context,
46
- values: Record<string, FormDataValue>
47
- ): Promise<{
48
- valid: boolean;
49
- field_errors: Partial<Record<keyof Fields, string>>;
50
- form_messages: FormMessage[];
51
- }> {
52
- const field_errors = {} as Record<keyof Fields, string>;
53
- let valid = true;
54
- const form_messages = [] as FormMessage[];
55
- await Promise.all(
56
- Object.entries(values)
57
- .filter(([fieldname]) => fieldname in this.fields)
58
- .map(async ([key]: [keyof F, FormDataValue]) => {
59
- const field = this.fields[key];
60
- const { valid: fieldvalid, message: fieldmessage } =
61
- await field.getValue(ctx, values);
62
- if (!fieldvalid) {
63
- valid = false;
64
- field_errors[field.name] = fieldmessage;
65
- }
66
- })
67
- );
68
- const formValidationResult = await this.validateValues(ctx, values);
69
- if (!formValidationResult.valid) {
70
- form_messages.push({
71
- type: "error",
72
- text: formValidationResult.error,
73
- });
74
- valid = false;
75
- }
76
- return { valid, field_errors, form_messages };
77
- }
78
-
79
- init(): void {
80
- for (const [name, field] of Object.entries(this.fields)) {
81
- field.init(name);
82
- }
83
- }
84
-
85
- public renderControls(
86
- ctx: Context,
87
- data: FormData<keyof Fields>
88
- ): FlatTemplatable {
89
- return tempstream/* HTML */ `${this.controls.map((control) =>
90
- control.render(ctx, data.raw_values, data.messages)
91
- )}`;
92
- }
93
-
94
- public renderMessages(
95
- _: Context,
96
- data: FormData<keyof Fields>
97
- ): FlatTemplatable {
98
- return tempstream/* HTML */ `<div class="form-messages">
99
- ${data.messages.map(
100
- (message) =>
101
- `<div class="form-message form-message--${message.type}">${message.text}</div>`
102
- )}
103
- </div>`;
104
- }
105
13
  }
package/src/page/page.ts CHANGED
@@ -1,15 +1,19 @@
1
1
  import Router from "@koa/router";
2
2
  import { Context } from "koa";
3
3
 
4
- import { FlatTemplatable, Templatable, tempstream } from "tempstream";
4
+ import { FlatTemplatable, tempstream } from "tempstream";
5
5
  import { AllQueryParams } from "..";
6
6
  import { FormDataValue } from "../forms/form";
7
- import { Fields, Mountable, PageErrorMessage } from "./mountable";
7
+ import {
8
+ Fields,
9
+ MountableWithFields,
10
+ PageErrorMessage,
11
+ } from "./mountable-with-fields";
8
12
  import { PagePropsParser } from "./props-parser";
9
13
 
10
14
  export abstract class Page<
11
15
  F extends Fields = Record<string, never>
12
- > extends Mountable<F> {
16
+ > extends MountableWithFields<F> {
13
17
  propsParser: PagePropsParser<Record<keyof Fields, unknown>> =
14
18
  new AllQueryParams();
15
19
  controls = [];
@@ -0,0 +1,58 @@
1
+ import { curryImportPath } from "../utils/utils";
2
+
3
+ export async function multiformTemplate(
4
+ action_name: string,
5
+ newfilefullpath: string
6
+ ): Promise<string> {
7
+ const rel = curryImportPath(newfilefullpath);
8
+ return `import { Context } from "koa";
9
+ import html from "${rel("src/back/html")}";
10
+ import { Controls, Fields, Form, FormMessage, Multiform } from "@sealcode/sealgen";
11
+ import { FlatTemplatable } from "tempstream";
12
+
13
+ export const actionName = "${action_name}";
14
+
15
+ const fields1 = {
16
+ email: new Fields.SimpleFormField(true),
17
+ password: new Fields.SimpleFormField(true),
18
+ };
19
+
20
+ const form1 = new (class extends Form<typeof fields1> {
21
+ defaultSuccessMessage = "Form1 done!";
22
+ controls = [
23
+ new Controls.FormHeader("form1"),
24
+ new Controls.SimpleInput(fields1.email),
25
+ ];
26
+ fields = fields1;
27
+ onSubmit() {
28
+ console.log("form1 submit");
29
+ }
30
+ })();
31
+
32
+ const fields2 = {
33
+ name: new Fields.SimpleFormField(true),
34
+ surname: new Fields.SimpleFormField(true),
35
+ };
36
+
37
+ const form2 = new (class extends Form<Record<never, unknown>> {
38
+ defaultSuccessMessage = "Form2 done!";
39
+ controls = [new Controls.FormHeader("form2"), new Controls.SimpleInput(fields2.name)];
40
+ fields = fields2;
41
+ onSubmit() {
42
+ console.log("form2 submit");
43
+ }
44
+ })();
45
+
46
+ export default new (class extends Multiform {
47
+ name = "multiform_test";
48
+ forms = { form1, form2 };
49
+ async render(
50
+ ctx: Context,
51
+ messages: FormMessage[],
52
+ prerenderedForms?: Record<string, FlatTemplatable | undefined>
53
+ ): Promise<FlatTemplatable> {
54
+ return html(ctx, "${action_name}", await super.render(ctx, messages, prerenderedForms));
55
+ }
56
+ })();
57
+ `;
58
+ }
@@ -0,0 +1,22 @@
1
+ export async function redirectTemplate(action_name: string): Promise<string> {
2
+ return `import { Context } from "koa";
3
+ import { Mountable } from "@sealcode/sealgen";
4
+ import Router from "@koa/router";
5
+
6
+ export const actionName = "${action_name}";
7
+
8
+ export default new (class ${action_name}Redirect extends Mountable {
9
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
10
+ async canAccess(_: Context) {
11
+ return { canAccess: true, message: "" };
12
+ }
13
+
14
+ mount(router: Router, path: string) {
15
+ router.get(path, async (ctx) => {
16
+ ctx.status = 302;
17
+ ctx.redirect("/new_url");
18
+ });
19
+ }
20
+ })();
21
+ `;
22
+ }
@@ -0,0 +1,106 @@
1
+ import Koa, { Context } from "koa";
2
+ import Router from "@koa/router";
3
+ import { Controls, Fields, Form } from "../..";
4
+ import { Multiform } from "../../forms/multiform";
5
+ import koaBody from "koa-body";
6
+ import { FlatTemplatable, tempstream } from "tempstream";
7
+ import { App } from "sealious";
8
+ import { FormMessage } from "../../forms/form";
9
+ const app = new Koa({ proxy: true });
10
+ const router = new Router();
11
+
12
+ const fields1 = {
13
+ email: new Fields.SimpleFormField(true),
14
+ password: new Fields.SimpleFormField(true),
15
+ };
16
+
17
+ const form1 = new (class extends Form<typeof fields1> {
18
+ defaultSuccessMessage = "Form1 done!";
19
+ controls = [
20
+ new Controls.FormHeader("form1"),
21
+ new Controls.SimpleInput(fields1.email),
22
+ ];
23
+ fields = fields1;
24
+ onSubmit() {
25
+ console.log("form1 submit");
26
+ }
27
+ })();
28
+
29
+ const fields2 = {
30
+ name: new Fields.SimpleFormField(true),
31
+ surname: new Fields.SimpleFormField(true),
32
+ };
33
+
34
+ const form2 = new (class extends Form<Record<never, unknown>> {
35
+ defaultSuccessMessage = "Form2 done!";
36
+ controls = [
37
+ new Controls.FormHeader("form2"),
38
+ new Controls.SimpleInput(fields2.name),
39
+ ];
40
+ fields = fields2;
41
+ onSubmit() {
42
+ console.log("form2 submit");
43
+ }
44
+ })();
45
+
46
+ const multiform = new (class extends Multiform {
47
+ name = "multiform_test";
48
+ forms = { form1, form2 };
49
+ async render(
50
+ ctx: Koa.Context,
51
+ messages: FormMessage[],
52
+ prerenderedForms?: Record<string, FlatTemplatable | undefined>
53
+ ): Promise<FlatTemplatable> {
54
+ return tempstream`<!DOCTYPE html>
55
+ <html>
56
+ <head>
57
+ <title>Multiform Test - Sealgen</title>
58
+ <script type="module">
59
+ import hotwiredTurbo from "https://cdn.skypack.dev/pin/@hotwired/turbo@v7.0.0-9XNv1By6uiqRlBzQLO2d/mode=imports,min/optimized/@hotwired/turbo.js";
60
+ </script>
61
+ </head>
62
+ <body>
63
+ ${super.render(ctx, messages, prerenderedForms)}
64
+ </body>
65
+ </html>`;
66
+ }
67
+ })();
68
+
69
+ const URL = "/";
70
+
71
+ async function handleHtmlPromise(ctx: Context, next: Koa.Next) {
72
+ await next();
73
+ if (ctx.body instanceof Promise) {
74
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
75
+ ctx.body = await ctx.body;
76
+ }
77
+ ctx.set("content-type", "text/html;charset=utf-8");
78
+ }
79
+
80
+ router.use(
81
+ URL,
82
+ koaBody(),
83
+ async (ctx, next) => {
84
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
85
+ ctx.$body = ctx.request.body;
86
+ ctx.$app = {
87
+ getString: (
88
+ _: unknown,
89
+ args: unknown[],
90
+ trans: string | ((...args: unknown[]) => string)
91
+ ) => (typeof trans == "function" ? trans(args) : trans),
92
+ } as unknown as App;
93
+ await next();
94
+ },
95
+ handleHtmlPromise
96
+ );
97
+ multiform.init();
98
+ multiform.mount(router, URL);
99
+ // to automatically add trailing slashes:
100
+
101
+ router.use(URL, (ctx) => ctx.set("content-type", "text/html;charset=utf-8"));
102
+
103
+ app.use(router.routes()).use(router.allowedMethods());
104
+ app.listen(3000);
105
+
106
+ console.log("Multiform test app listening on localhost:3000");
@@ -0,0 +1,18 @@
1
+ import { Readable } from "stream";
2
+ import { tempstream } from "tempstream";
3
+
4
+ export function getRequiredClass(isRequired: boolean): string {
5
+ return isRequired ? "required" : "";
6
+ }
7
+
8
+ function getModifierClasses(modifiers: string[]) {
9
+ return modifiers.map((m) => "form-input__wrapper--" + m).join(" ");
10
+ }
11
+
12
+ export function inputWrapper(modifiers: string[], input: string): Readable {
13
+ return tempstream/* HTML */ `<div
14
+ class="form-input form-input__wrapper ${getModifierClasses(modifiers)}"
15
+ >
16
+ ${input}
17
+ </div>`;
18
+ }