@sealcode/sealgen 0.14.0 → 0.14.3

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/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 20
@@ -7,6 +7,7 @@ import { Mountable } from "./mountable.js";
7
7
  export type StatefulPageActionDescription<ActionName> = {
8
8
  action: ActionName;
9
9
  label?: string;
10
+ content?: string;
10
11
  } | ActionName;
11
12
  export type StateAndMetadata<State, Actions> = {
12
13
  state: State;
@@ -34,6 +35,7 @@ export declare abstract class StatefulPage<State extends Record<string, unknown>
34
35
  extractActionAndLabel<ActionName extends keyof Actions>(action_description: StatefulPageActionDescription<ActionName>): {
35
36
  action: string;
36
37
  label: string;
38
+ content: string;
37
39
  };
38
40
  makeActionURL<ActionName extends keyof Actions>(action_description: StatefulPageActionDescription<ActionName>, ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>): string;
39
41
  makeActionButton<ActionName extends keyof Actions>(_state: State, action_description: StatefulPageActionDescription<ActionName>, ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>): string;
@@ -42,7 +44,8 @@ export declare abstract class StatefulPage<State extends Record<string, unknown>
42
44
  label?: string;
43
45
  } | ActionName, ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>): string;
44
46
  rerender(): string;
45
- preprocessRequestBody(values: Record<string, unknown>): Promise<StateAndMetadata<State, Actions>>;
47
+ preprocessState(values: State): Promise<State>;
48
+ preprocessOverrides(state: State, values: Record<string, unknown>): Promise<Record<string, unknown>>;
46
49
  extractState(ctx: BaseContext): Promise<StateAndMetadata<State, Actions>>;
47
50
  mount(router: Router, path: string): void;
48
51
  }
@@ -1,4 +1,4 @@
1
- import { predicates, hasShape, is, hasField } from "@sealcode/ts-predicates";
1
+ import { predicates, hasShape, is } from "@sealcode/ts-predicates";
2
2
  import deepmerge from "deepmerge";
3
3
  import { tempstream } from "tempstream";
4
4
  import { from_base64, to_base64 } from "../utils/base64.js";
@@ -31,15 +31,17 @@ class StatefulPage extends Mountable {
31
31
  </form>`;
32
32
  }
33
33
  extractActionAndLabel(action_description) {
34
- let label, action;
34
+ let label, action, content;
35
35
  if (is(action_description, predicates.object)) {
36
36
  action = action_description.action.toString();
37
37
  label = action_description.label || action;
38
+ content = action_description.content || label;
38
39
  } else {
39
40
  action = action_description.toString();
40
41
  label = action;
42
+ content = label;
41
43
  }
42
- return { action, label };
44
+ return { action, label, content };
43
45
  }
44
46
  makeActionURL(action_description, ...args) {
45
47
  const { action } = this.extractActionAndLabel(action_description);
@@ -48,15 +50,17 @@ class StatefulPage extends Mountable {
48
50
  )}`;
49
51
  }
50
52
  makeActionButton(_state, action_description, ...args) {
51
- const { label } = this.extractActionAndLabel(action_description);
53
+ let { label, content } = this.extractActionAndLabel(action_description);
52
54
  return (
53
55
  /* HTML */
54
56
  `
55
- <input
57
+ <button
56
58
  type="submit"
57
- value="${label}"
58
59
  formaction="${this.makeActionURL(action_description, ...args)}"
59
- />
60
+ title="${label}"
61
+ >
62
+ ${content}
63
+ </button>
60
64
  `
61
65
  );
62
66
  }
@@ -69,7 +73,10 @@ class StatefulPage extends Mountable {
69
73
  rerender() {
70
74
  return "this.closest('form').requestSubmit()";
71
75
  }
72
- async preprocessRequestBody(values) {
76
+ async preprocessState(values) {
77
+ return values;
78
+ }
79
+ async preprocessOverrides(state, values) {
73
80
  return values;
74
81
  }
75
82
  async extractState(ctx) {
@@ -85,31 +92,50 @@ class StatefulPage extends Mountable {
85
92
  console.error("Wrong data: ", ctx.$body);
86
93
  throw new Error("wrong formdata shape");
87
94
  }
88
- const prepared_body = await this.preprocessRequestBody(ctx.$body);
89
95
  const inputs = Object.fromEntries(
90
- Object.entries(prepared_body).filter(
96
+ Object.entries(ctx.$body).filter(
91
97
  ([key]) => !["action", "state", "args", "$"].includes(key)
92
98
  )
93
99
  );
94
- const original_state_string = prepared_body.state;
100
+ const original_state_string = ctx.$body.state;
95
101
  const original_state = JSON.parse(
96
102
  typeof original_state_string == "string" ? from_base64(original_state_string) : "{}"
97
103
  );
98
- const modified_state = deepmerge(
104
+ const $body = ctx.$body;
105
+ let state_overrides = $body.$ || {};
106
+ state_overrides = await this.preprocessOverrides(
99
107
  original_state,
100
- hasField("$", prepared_body) ? prepared_body.$ : {},
101
- {
102
- arrayMerge: (_target, source, _options) => {
103
- return source;
104
- }
105
- }
108
+ state_overrides
106
109
  );
107
- if (prepared_body.action && prepared_body.action_args) {
110
+ let modified_state = deepmerge(original_state, state_overrides, {
111
+ arrayMerge: (target, source, options) => {
112
+ const destination = target.slice();
113
+ source.forEach((item, index) => {
114
+ if (typeof destination[index] === "undefined") {
115
+ destination[index] = options.cloneUnlessOtherwiseSpecified(
116
+ item,
117
+ options
118
+ );
119
+ } else if (options.isMergeableObject(item)) {
120
+ destination[index] = deepmerge(
121
+ target[index],
122
+ item,
123
+ options
124
+ );
125
+ } else if (target.indexOf(item) === -1) {
126
+ destination.push(item);
127
+ }
128
+ });
129
+ return destination;
130
+ }
131
+ });
132
+ modified_state = await this.preprocessState(modified_state);
133
+ if (ctx.$body.action && ctx.$body.action_args) {
108
134
  return {
109
135
  state: modified_state,
110
- action: prepared_body.action,
136
+ action: ctx.$body.action,
111
137
  inputs,
112
- action_args: prepared_body.action_args
138
+ action_args: ctx.$body.action_args
113
139
  };
114
140
  } else {
115
141
  return {
@@ -117,7 +143,7 @@ class StatefulPage extends Mountable {
117
143
  action: null,
118
144
  inputs,
119
145
  action_args: null,
120
- $: hasField("$", prepared_body) ? prepared_body.$ : {}
146
+ $: ctx.$body.$ || {}
121
147
  };
122
148
  }
123
149
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sealcode/sealgen",
3
- "version": "0.14.0",
3
+ "version": "0.14.3",
4
4
  "description": "Module to automate adding routes and collections to a sealious application",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -71,6 +71,6 @@
71
71
  "peerDependencies": {
72
72
  "koa": "^2.13.0",
73
73
  "koa-responsive-image-router": "^0.2.6",
74
- "sealious": "^0.18.0"
74
+ "sealious": "^0.18.1"
75
75
  }
76
76
  }
@@ -11,6 +11,7 @@ export type StatefulPageActionDescription<ActionName> =
11
11
  | {
12
12
  action: ActionName;
13
13
  label?: string;
14
+ content?: string;
14
15
  }
15
16
  | ActionName;
16
17
 
@@ -85,16 +86,18 @@ export abstract class StatefulPage<
85
86
 
86
87
  extractActionAndLabel<ActionName extends keyof Actions>(
87
88
  action_description: StatefulPageActionDescription<ActionName>
88
- ): { action: string; label: string } {
89
- let label, action: string;
89
+ ): { action: string; label: string; content: string } {
90
+ let label, action, content: string;
90
91
  if (is(action_description, predicates.object)) {
91
92
  action = action_description.action.toString();
92
93
  label = action_description.label || action;
94
+ content = action_description.content || label;
93
95
  } else {
94
96
  action = action_description.toString();
95
97
  label = action;
98
+ content = label;
96
99
  }
97
- return { action, label };
100
+ return { action, label, content };
98
101
  }
99
102
 
100
103
  makeActionURL<ActionName extends keyof Actions>(
@@ -112,13 +115,15 @@ export abstract class StatefulPage<
112
115
  action_description: StatefulPageActionDescription<ActionName>,
113
116
  ...args: ExtractTail<ExtractTail<Parameters<Actions[ActionName]>>>
114
117
  ) {
115
- const { label } = this.extractActionAndLabel(action_description);
118
+ let { label, content } = this.extractActionAndLabel(action_description);
116
119
  return /* HTML */ `
117
- <input
120
+ <button
118
121
  type="submit"
119
- value="${label}"
120
122
  formaction="${this.makeActionURL(action_description, ...args)}"
121
- />
123
+ title="${label}"
124
+ >
125
+ ${content}
126
+ </button>
122
127
  `;
123
128
  }
124
129
 
@@ -141,10 +146,15 @@ export abstract class StatefulPage<
141
146
  return "this.closest('form').requestSubmit()";
142
147
  }
143
148
 
144
- async preprocessRequestBody(
149
+ async preprocessState(values: State): Promise<State> {
150
+ return values;
151
+ }
152
+
153
+ async preprocessOverrides(
154
+ state: State,
145
155
  values: Record<string, unknown>
146
- ): Promise<StateAndMetadata<State, Actions>> {
147
- return values as StateAndMetadata<State, Actions>;
156
+ ): Promise<Record<string, unknown>> {
157
+ return values;
148
158
  }
149
159
 
150
160
  async extractState(
@@ -164,43 +174,66 @@ export abstract class StatefulPage<
164
174
  console.error("Wrong data: ", ctx.$body);
165
175
  throw new Error("wrong formdata shape");
166
176
  }
167
- // giving extending classes a change to modify body before furhter processing
168
- const prepared_body = await this.preprocessRequestBody(ctx.$body);
177
+
169
178
  const inputs = Object.fromEntries(
170
- Object.entries(prepared_body).filter(
179
+ Object.entries(ctx.$body).filter(
171
180
  ([key]) => !["action", "state", "args", "$"].includes(key)
172
181
  )
173
182
  ) as Record<string, string>;
174
183
  // the "$" key is parsed as dot notation and overrides the state
175
184
 
176
- const original_state_string = prepared_body.state;
185
+ const original_state_string = ctx.$body.state;
177
186
  const original_state = JSON.parse(
178
187
  typeof original_state_string == "string"
179
188
  ? from_base64(original_state_string)
180
189
  : "{}"
181
190
  );
182
191
 
183
- const modified_state = deepmerge(
192
+ const $body = ctx.$body;
193
+ let state_overrides = $body.$ || {};
194
+ state_overrides = await this.preprocessOverrides(
184
195
  original_state,
185
- hasField("$", prepared_body) ? prepared_body.$ : {},
186
- {
187
- arrayMerge: (
188
- _target: any[],
189
- source: any[],
190
- _options: ArrayMergeOptions
191
- ) => {
192
- // https://github.com/TehShrike/deepmerge#user-content-arraymerge-example-combine-arrays
193
- return source;
194
- },
195
- }
196
- ) as State;
196
+ state_overrides
197
+ );
198
+ let modified_state = deepmerge(original_state, state_overrides, {
199
+ arrayMerge: (
200
+ target: any[],
201
+ source: any[],
202
+ options: ArrayMergeOptions
203
+ ) => {
204
+ // https://github.com/TehShrike/deepmerge?tab=readme-ov-file#arraymerge-example-combine-arrays
205
+ const destination = target.slice();
206
+
207
+ source.forEach((item, index) => {
208
+ if (typeof destination[index] === "undefined") {
209
+ destination[index] =
210
+ options.cloneUnlessOtherwiseSpecified(
211
+ item,
212
+ options
213
+ );
214
+ } else if (options.isMergeableObject(item)) {
215
+ destination[index] = deepmerge(
216
+ target[index],
217
+ item,
218
+ options
219
+ );
220
+ } else if (target.indexOf(item) === -1) {
221
+ destination.push(item);
222
+ }
223
+ });
224
+ return destination;
225
+ },
226
+ }) as State;
227
+
228
+ // giving extending classes a change to modify the state before furhter processing
229
+ modified_state = await this.preprocessState(modified_state);
197
230
 
198
- if (prepared_body.action && prepared_body.action_args) {
231
+ if (ctx.$body.action && ctx.$body.action_args) {
199
232
  return {
200
233
  state: modified_state,
201
- action: prepared_body.action as string,
234
+ action: ctx.$body.action as string,
202
235
  inputs,
203
- action_args: prepared_body.action_args,
236
+ action_args: ctx.$body.action_args,
204
237
  };
205
238
  } else {
206
239
  return {
@@ -208,7 +241,7 @@ export abstract class StatefulPage<
208
241
  action: null,
209
242
  inputs,
210
243
  action_args: null,
211
- $: hasField("$", prepared_body) ? prepared_body.$ : {},
244
+ $: ctx.$body.$ || {},
212
245
  };
213
246
  }
214
247
  }