create-foldkit-app 0.29.0 → 0.29.1

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 (68) hide show
  1. package/dist/templates/base/AGENTS.md +5 -112
  2. package/dist/templates/base/FOLDKIT.md +144 -0
  3. package/dist/templates/examples/api-cache/src/main.ts +36 -40
  4. package/dist/templates/examples/auth/src/main.ts +16 -15
  5. package/dist/templates/examples/auth/src/page/loggedIn/update.ts +10 -11
  6. package/dist/templates/examples/auth/src/page/loggedOut/page/login.ts +44 -55
  7. package/dist/templates/examples/auth/src/page/loggedOut/update.ts +8 -12
  8. package/dist/templates/examples/auth/src/update.ts +67 -73
  9. package/dist/templates/examples/canvas-art/src/main.ts +32 -31
  10. package/dist/templates/examples/charting/src/init.test.ts +6 -3
  11. package/dist/templates/examples/charting/src/init.ts +4 -4
  12. package/dist/templates/examples/charting/src/update.ts +34 -38
  13. package/dist/templates/examples/counter/src/main.ts +13 -13
  14. package/dist/templates/examples/counters/src/counter.ts +9 -8
  15. package/dist/templates/examples/counters/src/main.ts +20 -26
  16. package/dist/templates/examples/crash-view/src/main.ts +5 -3
  17. package/dist/templates/examples/embedding/src/main.ts +11 -9
  18. package/dist/templates/examples/form/src/main.ts +79 -89
  19. package/dist/templates/examples/generative-art/src/main.ts +6 -4
  20. package/dist/templates/examples/generative-art/src/update.ts +26 -23
  21. package/dist/templates/examples/interrupting-commands/src/main.ts +68 -66
  22. package/dist/templates/examples/job-application/src/main.ts +3 -4
  23. package/dist/templates/examples/job-application/src/step/attachments.ts +16 -19
  24. package/dist/templates/examples/job-application/src/step/coverLetter.ts +5 -5
  25. package/dist/templates/examples/job-application/src/step/education/education.ts +12 -17
  26. package/dist/templates/examples/job-application/src/step/education/entry.ts +29 -42
  27. package/dist/templates/examples/job-application/src/step/personalInfo.ts +35 -44
  28. package/dist/templates/examples/job-application/src/step/skills/entry.ts +15 -21
  29. package/dist/templates/examples/job-application/src/step/skills/skills.ts +12 -17
  30. package/dist/templates/examples/job-application/src/step/workHistory/entry.ts +41 -58
  31. package/dist/templates/examples/job-application/src/step/workHistory/workHistory.ts +12 -17
  32. package/dist/templates/examples/job-application/src/update.ts +26 -29
  33. package/dist/templates/examples/kanban/src/main.ts +3 -4
  34. package/dist/templates/examples/kanban/src/update.ts +31 -31
  35. package/dist/templates/examples/managed-resource-layer/src/main.ts +30 -34
  36. package/dist/templates/examples/map/src/main.ts +48 -56
  37. package/dist/templates/examples/pixel-art/src/comparison.bench.test.ts +34 -13
  38. package/dist/templates/examples/pixel-art/src/main.bench.ts +3 -3
  39. package/dist/templates/examples/pixel-art/src/main.ts +3 -4
  40. package/dist/templates/examples/pixel-art/src/update.ts +109 -100
  41. package/dist/templates/examples/query-sync/src/main.ts +27 -25
  42. package/dist/templates/examples/route-transitions/src/main.ts +46 -43
  43. package/dist/templates/examples/route-transitions/src/story.test.ts +11 -11
  44. package/dist/templates/examples/routing/src/main.ts +16 -14
  45. package/dist/templates/examples/routing/src/page/people.ts +23 -28
  46. package/dist/templates/examples/shopping-cart/src/main.ts +41 -42
  47. package/dist/templates/examples/shopping-cart/src/page/products.ts +28 -35
  48. package/dist/templates/examples/slow-warnings/src/main.ts +23 -32
  49. package/dist/templates/examples/snake/src/main.ts +135 -143
  50. package/dist/templates/examples/ssg/src/main.ts +20 -17
  51. package/dist/templates/examples/ssr/src/main.ts +20 -24
  52. package/dist/templates/examples/state-machine/src/main.ts +24 -11
  53. package/dist/templates/examples/state-machine/src/story.test.ts +2 -2
  54. package/dist/templates/examples/stopwatch/src/main.ts +42 -50
  55. package/dist/templates/examples/todo/src/main.ts +152 -160
  56. package/dist/templates/examples/ui-showcase/src/main.ts +53 -54
  57. package/dist/templates/examples/ui-showcase/src/scene.test.ts +2 -2
  58. package/dist/templates/examples/ui-showcase/src/story.test.ts +2 -2
  59. package/dist/templates/examples/ui-showcase/src/ui/init.ts +6 -7
  60. package/dist/templates/examples/ui-showcase/src/ui/update.ts +265 -336
  61. package/dist/templates/examples/view-transitions/src/main.ts +18 -17
  62. package/dist/templates/examples/weather/src/main.ts +33 -40
  63. package/dist/templates/examples/web-components/src/main.ts +14 -17
  64. package/dist/templates/examples/websocket-chat/src/main.ts +44 -50
  65. package/dist/templates/release.json +8 -8
  66. package/dist/templates/rendering/ssg/src/main.ts +20 -17
  67. package/dist/templates/rendering/ssr/src/main.ts +20 -24
  68. package/package.json +1 -1
@@ -147,8 +147,8 @@ const nextSequenceNumber = (
147
147
 
148
148
  const logTransition =
149
149
  (transition: AppTransition): Step =>
150
- model => [
151
- evo(model, {
150
+ model => ({
151
+ model: evo(model, {
152
152
  transitionLog: transitionLog =>
153
153
  pipe(
154
154
  transitionLog,
@@ -160,54 +160,58 @@ const logTransition =
160
160
  Array.take(MAX_LOGGED_TRANSITIONS),
161
161
  ),
162
162
  }),
163
- [],
164
- ]
163
+ })
165
164
 
166
165
  const loadCatalogOnGalleryEntry =
167
166
  (transition: AppTransition): Step =>
168
167
  model =>
169
168
  Transition.isEntering(transition, 'Gallery') &&
170
169
  model.catalogStatus !== 'Loading'
171
- ? [evo(model, { catalogStatus: () => 'Loading' }), [LoadCatalog()]]
172
- : [model, []]
170
+ ? {
171
+ model: evo(model, { catalogStatus: () => 'Loading' }),
172
+ commands: [LoadCatalog()],
173
+ }
174
+ : { model }
173
175
 
174
176
  const loadPaintingOnEntry =
175
177
  (transition: AppTransition): Step =>
176
178
  model =>
177
179
  Option.match(Transition.entered(transition, 'Painting'), {
178
- onNone: () => [model, []],
179
- onSome: ({ paintingId }) => [
180
- evo(model, { paintingStatus: () => PaintingLoading({ paintingId }) }),
181
- [LoadPainting({ paintingId })],
182
- ],
180
+ onNone: () => ({ model }),
181
+ onSome: ({ paintingId }) => ({
182
+ model: evo(model, {
183
+ paintingStatus: () => PaintingLoading({ paintingId }),
184
+ }),
185
+ commands: [LoadPainting({ paintingId })],
186
+ }),
183
187
  })
184
188
 
185
189
  const reloadPaintingOnIdChange =
186
190
  (transition: AppTransition): Step =>
187
191
  model =>
188
192
  Option.match(Transition.stayed(transition, 'Painting'), {
189
- onNone: () => [model, []],
193
+ onNone: () => ({ model }),
190
194
  onSome: ({ previousRoute, nextRoute }) =>
191
195
  previousRoute.paintingId === nextRoute.paintingId
192
- ? [model, []]
193
- : [
194
- evo(model, {
196
+ ? { model }
197
+ : {
198
+ model: evo(model, {
195
199
  paintingStatus: () =>
196
200
  PaintingLoading({ paintingId: nextRoute.paintingId }),
197
201
  }),
198
- [LoadPainting({ paintingId: nextRoute.paintingId })],
199
- ],
202
+ commands: [LoadPainting({ paintingId: nextRoute.paintingId })],
203
+ },
200
204
  })
201
205
 
202
206
  const saveDraftOnStudioExit =
203
207
  (transition: AppTransition): Step =>
204
208
  model =>
205
209
  Option.match(Transition.exited(transition, 'Studio'), {
206
- onNone: () => [model, []],
210
+ onNone: () => ({ model }),
207
211
  onSome: () =>
208
212
  model.studioDraft === ''
209
- ? [model, []]
210
- : [model, [SaveDraft({ draft: model.studioDraft })]],
213
+ ? { model }
214
+ : { model, commands: [SaveDraft({ draft: model.studioDraft })] },
211
215
  })
212
216
 
213
217
  const handleTransition = (
@@ -224,18 +228,21 @@ const handleTransition = (
224
228
 
225
229
  export const update = (model: Model, message: Message) =>
226
230
  Message.match<UpdateReturn>(message, {
227
- CompletedNavigateInternal: () => [model, []],
228
- CompletedLoadExternal: () => [model, []],
231
+ CompletedNavigateInternal: () => ({ model }),
232
+ CompletedLoadExternal: () => ({ model }),
229
233
 
230
234
  ClickedLink: ({ request }) =>
231
235
  M.value(request).pipe(
232
236
  withUpdateReturn,
233
237
  M.tagsExhaustive({
234
- Internal: ({ url }) => [
238
+ Internal: ({ url }) => ({
235
239
  model,
236
- [NavigateInternal({ url: urlToString(url) })],
237
- ],
238
- External: ({ href }) => [model, [LoadExternal({ href })]],
240
+ commands: [NavigateInternal({ url: urlToString(url) })],
241
+ }),
242
+ External: ({ href }) => ({
243
+ model,
244
+ commands: [LoadExternal({ href })],
245
+ }),
239
246
  }),
240
247
  ),
241
248
 
@@ -248,31 +255,27 @@ export const update = (model: Model, message: Message) =>
248
255
  )
249
256
  },
250
257
 
251
- SucceededLoadCatalog: () => [
252
- evo(model, { catalogStatus: () => 'Ready' }),
253
- [],
254
- ],
258
+ SucceededLoadCatalog: () => ({
259
+ model: evo(model, { catalogStatus: () => 'Ready' }),
260
+ }),
255
261
 
256
262
  SucceededLoadPainting: ({ paintingId }) =>
257
263
  model.paintingStatus._tag === 'PaintingLoading' &&
258
264
  model.paintingStatus.paintingId === paintingId
259
- ? [
260
- evo(model, {
265
+ ? {
266
+ model: evo(model, {
261
267
  paintingStatus: () => PaintingReady({ paintingId }),
262
268
  }),
263
- [],
264
- ]
265
- : [model, []],
269
+ }
270
+ : { model },
266
271
 
267
- UpdatedStudioDraft: ({ value }) => [
268
- evo(model, { studioDraft: () => value }),
269
- [],
270
- ],
272
+ UpdatedStudioDraft: ({ value }) => ({
273
+ model: evo(model, { studioDraft: () => value }),
274
+ }),
271
275
 
272
- SucceededSaveDraft: ({ draft }) => [
273
- evo(model, { maybeSavedDraft: () => Option.some(draft) }),
274
- [],
275
- ],
276
+ SucceededSaveDraft: ({ draft }) => ({
277
+ model: evo(model, { maybeSavedDraft: () => Option.some(draft) }),
278
+ }),
276
279
  })
277
280
 
278
281
  // INIT
@@ -40,26 +40,26 @@ const modelOn = (route: AppRoute): Model =>
40
40
 
41
41
  describe('init', () => {
42
42
  test('a cold load into the gallery logs the transition and loads the catalog', () => {
43
- const [model, commands] = init(urlOrThrow('http://localhost/gallery'))
43
+ const init_ = init(urlOrThrow('http://localhost/gallery'))
44
44
 
45
- expect(model.route._tag).toBe('Gallery')
46
- expect(model.catalogStatus).toBe('Loading')
47
- expect(model.transitionLog).toHaveLength(1)
45
+ expect(init_.model.route._tag).toBe('Gallery')
46
+ expect(init_.model.catalogStatus).toBe('Loading')
47
+ expect(init_.model.transitionLog).toHaveLength(1)
48
48
  expect(
49
- Array.map(model.transitionLog, entry => entry.maybePreviousRoute),
49
+ Array.map(init_.model.transitionLog, entry => entry.maybePreviousRoute),
50
50
  ).toStrictEqual([Option.none()])
51
- expect(Array.map(commands, command => command.name)).toContain(
51
+ expect(Array.map(init_.commands ?? [], command => command.name)).toContain(
52
52
  'LoadCatalog',
53
53
  )
54
54
  })
55
55
 
56
56
  test('a cold load into the home route loads nothing', () => {
57
- const [model, commands] = init(urlOrThrow('http://localhost/'))
57
+ const homeInit = init(urlOrThrow('http://localhost/'))
58
58
 
59
- expect(model.route._tag).toBe('Home')
60
- expect(model.catalogStatus).toBe('Idle')
61
- expect(model.transitionLog).toHaveLength(1)
62
- expect(commands).toStrictEqual([])
59
+ expect(homeInit.model.route._tag).toBe('Home')
60
+ expect(homeInit.model.catalogStatus).toBe('Idle')
61
+ expect(homeInit.model.transitionLog).toHaveLength(1)
62
+ expect(homeInit.commands ?? []).toStrictEqual([])
63
63
  })
64
64
  })
65
65
 
@@ -69,14 +69,13 @@ export const init: Runtime.RoutingApplicationInit<Model, Message> = (
69
69
  M.orElse(() => PeopleRoute({ searchText: Option.none() })),
70
70
  )
71
71
 
72
- const [peoplePage, peopleCommands] = People.init(initialPeopleRoute)
73
-
74
- return [
75
- { route, peoplePage },
76
- Command.mapMessages(peopleCommands, childMessage =>
72
+ const peopleInit = People.init(initialPeopleRoute)
73
+ return {
74
+ model: { route, peoplePage: peopleInit.model },
75
+ commands: Command.mapMessages(peopleInit.commands, childMessage =>
77
76
  Message.GotPeopleMessage({ message: childMessage }),
78
77
  ),
79
- ]
78
+ }
80
79
  }
81
80
 
82
81
  // COMMAND
@@ -97,7 +96,7 @@ const LoadExternal = Command.define('LoadExternal', {
97
96
 
98
97
  // UPDATE
99
98
 
100
- type UpdateReturn = readonly [Model, ReadonlyArray<Command.Command<Message>>]
99
+ type UpdateReturn = Update.Return<Model, Message>
101
100
  const withUpdateReturn = M.withReturnType<UpdateReturn>()
102
101
 
103
102
  const foldPeopleEntry = <Input>(
@@ -117,22 +116,25 @@ const foldPeopleRouteChanged = foldPeopleEntry(People.informRouteChanged)
117
116
 
118
117
  const setRoute =
119
118
  (nextRoute: AppRoute): Update.Step<Model, Message> =>
120
- model => [evo(model, { route: () => nextRoute }), []]
119
+ model => ({ model: evo(model, { route: () => nextRoute }) })
121
120
 
122
121
  export const update = (model: Model, message: Message) =>
123
122
  Message.match<UpdateReturn>(message, {
124
- CompletedNavigateInternal: () => [model, []],
125
- CompletedLoadExternal: () => [model, []],
123
+ CompletedNavigateInternal: () => ({ model }),
124
+ CompletedLoadExternal: () => ({ model }),
126
125
 
127
126
  ClickedLink: ({ request }) =>
128
127
  M.value(request).pipe(
129
128
  withUpdateReturn,
130
129
  M.tagsExhaustive({
131
- Internal: ({ url }) => [
130
+ Internal: ({ url }) => ({
132
131
  model,
133
- [NavigateInternal({ url: urlToString(url) })],
134
- ],
135
- External: ({ href }) => [model, [LoadExternal({ href })]],
132
+ commands: [NavigateInternal({ url: urlToString(url) })],
133
+ }),
134
+ External: ({ href }) => ({
135
+ model,
136
+ commands: [LoadExternal({ href })],
137
+ }),
136
138
  }),
137
139
  ),
138
140
 
@@ -8,7 +8,7 @@ import {
8
8
  String,
9
9
  pipe,
10
10
  } from 'effect'
11
- import { Command, Submodel } from 'foldkit'
11
+ import { Command, Submodel, type Update } from 'foldkit'
12
12
  import { Html, HtmlBuilder } from 'foldkit/html'
13
13
  import { defineMessageUnion } from 'foldkit/message'
14
14
  import { pushUrl } from 'foldkit/navigation'
@@ -110,18 +110,18 @@ export type Message = typeof Message.Type
110
110
 
111
111
  // INIT
112
112
 
113
- export const init = (
114
- route: PeopleRoute,
115
- ): readonly [Model, ReadonlyArray<Command.Command<Message>>] => {
113
+ type InitReturn = Update.Return<Model, Message>
114
+
115
+ export const init = (route: PeopleRoute): InitReturn => {
116
116
  const searchText = routeSearchText(route)
117
- return [
118
- {
117
+ return {
118
+ model: {
119
119
  searchInput: searchText,
120
120
  searchHistory: addSearchToHistory([], searchText),
121
121
  results: SearchLoading(),
122
122
  },
123
- [FetchPeople({ searchText })],
124
- ]
123
+ commands: [FetchPeople({ searchText })],
124
+ }
125
125
  }
126
126
 
127
127
  // COMMAND
@@ -151,48 +151,43 @@ export const FetchPeople = Command.define('FetchPeople', {
151
151
 
152
152
  // UPDATE
153
153
 
154
- export type UpdateReturn = readonly [
155
- Model,
156
- ReadonlyArray<Command.Command<Message>>,
157
- ]
154
+ export type UpdateReturn = Update.Return<Model, Message>
158
155
 
159
156
  export const update = (model: Model, message: Message) =>
160
157
  Message.match<UpdateReturn>(message, {
161
- ChangedSearchInput: ({ value }) => [
162
- evo(model, { searchInput: () => value }),
163
- [],
164
- ],
158
+ ChangedSearchInput: ({ value }) => ({
159
+ model: evo(model, { searchInput: () => value }),
160
+ }),
165
161
 
166
- SubmittedSearch: () => [
162
+ SubmittedSearch: () => ({
167
163
  model,
168
- [
164
+ commands: [
169
165
  PushSearchUrl({
170
166
  searchText: Option.fromNullishOr(model.searchInput || null),
171
167
  }),
172
168
  ],
173
- ],
169
+ }),
174
170
 
175
171
  ChangedRoute: ({ route }) => {
176
172
  const searchText = routeSearchText(route)
177
- return [
178
- evo(model, {
173
+ return {
174
+ model: evo(model, {
179
175
  searchInput: () => searchText,
180
176
  searchHistory: searchHistory =>
181
177
  addSearchToHistory(searchHistory, searchText),
182
178
  results: () => SearchLoading(),
183
179
  }),
184
- [FetchPeople({ searchText })],
185
- ]
180
+ commands: [FetchPeople({ searchText })],
181
+ }
186
182
  },
187
183
 
188
- SucceededFetchPeople: ({ query, people: fetchedPeople }) => [
189
- evo(model, {
184
+ SucceededFetchPeople: ({ query, people: fetchedPeople }) => ({
185
+ model: evo(model, {
190
186
  results: () => SearchLoaded({ query, people: fetchedPeople }),
191
187
  }),
192
- [],
193
- ],
188
+ }),
194
189
 
195
- CompletedPushSearchUrl: () => [model, []],
190
+ CompletedPushSearchUrl: () => ({ model }),
196
191
  })
197
192
 
198
193
  /** Tells the People page that the route changed. People does not own the
@@ -51,16 +51,15 @@ export type Message = typeof Message.Type
51
51
  export const init: Runtime.RoutingApplicationInit<Model, Message> = (
52
52
  url: Url,
53
53
  ) => {
54
- return [
55
- {
54
+ return {
55
+ model: {
56
56
  route: urlToAppRoute(url),
57
57
  cart: [],
58
58
  deliveryInstructions: '',
59
59
  orderPlaced: false,
60
60
  productsPage: Products.init(products),
61
61
  },
62
- [],
63
- ]
62
+ }
64
63
  }
65
64
 
66
65
  // COMMAND
@@ -81,7 +80,7 @@ const LoadExternal = Command.define('LoadExternal', {
81
80
 
82
81
  // UPDATE
83
82
 
84
- type UpdateReturn = readonly [Model, ReadonlyArray<Command.Command<Message>>]
83
+ type UpdateReturn = Update.Return<Model, Message>
85
84
  const withUpdateReturn = M.withReturnType<UpdateReturn>()
86
85
 
87
86
  const foldProductsOutMessage = M.type<Products.OutMessage>().pipe(
@@ -89,13 +88,17 @@ const foldProductsOutMessage = M.type<Products.OutMessage>().pipe(
89
88
  M.tagsExhaustive({
90
89
  AddedToCart:
91
90
  ({ item }) =>
92
- model => [evo(model, { cart: Cart.addItem(item) }), []],
91
+ model => ({ model: evo(model, { cart: Cart.addItem(item) }) }),
93
92
  IncrementedQuantity:
94
93
  ({ itemId }) =>
95
- model => [evo(model, { cart: Cart.incrementQuantity(itemId) }), []],
94
+ model => ({
95
+ model: evo(model, { cart: Cart.incrementQuantity(itemId) }),
96
+ }),
96
97
  DecrementedQuantity:
97
98
  ({ itemId }) =>
98
- model => [evo(model, { cart: Cart.decrementQuantity(itemId) }), []],
99
+ model => ({
100
+ model: evo(model, { cart: Cart.decrementQuantity(itemId) }),
101
+ }),
99
102
  }),
100
103
  )
101
104
 
@@ -110,74 +113,70 @@ const foldProducts = Update.foldChild({
110
113
 
111
114
  export const update = (model: Model, message: Message) =>
112
115
  Message.match<UpdateReturn>(message, {
113
- CompletedNavigateInternal: () => [model, []],
114
- CompletedLoadExternal: () => [model, []],
116
+ CompletedNavigateInternal: () => ({ model }),
117
+ CompletedLoadExternal: () => ({ model }),
115
118
 
116
119
  ClickedLink: ({ request }) =>
117
120
  M.value(request).pipe(
118
121
  withUpdateReturn,
119
122
  M.tagsExhaustive({
120
- Internal: ({ url }) => [
123
+ Internal: ({ url }) => ({
121
124
  model,
122
- [NavigateInternal({ url: urlToString(url) })],
123
- ],
125
+ commands: [NavigateInternal({ url: urlToString(url) })],
126
+ }),
124
127
 
125
- External: ({ href }) => [model, [LoadExternal({ href })]],
128
+ External: ({ href }) => ({
129
+ model,
130
+ commands: [LoadExternal({ href })],
131
+ }),
126
132
  }),
127
133
  ),
128
134
 
129
- ChangedUrl: ({ url }) => [
130
- evo(model, {
135
+ ChangedUrl: ({ url }) => ({
136
+ model: evo(model, {
131
137
  route: () => urlToAppRoute(url),
132
138
  }),
133
- [],
134
- ],
139
+ }),
135
140
 
136
141
  GotProductsMessage: ({ message }) => foldProducts(model, message),
137
142
 
138
- ClickedIncrementQuantity: ({ itemId }) => [
139
- evo(model, {
143
+ ClickedIncrementQuantity: ({ itemId }) => ({
144
+ model: evo(model, {
140
145
  cart: Cart.incrementQuantity(itemId),
141
146
  }),
142
- [],
143
- ],
147
+ }),
144
148
 
145
- ClickedDecrementQuantity: ({ itemId }) => [
146
- evo(model, {
149
+ ClickedDecrementQuantity: ({ itemId }) => ({
150
+ model: evo(model, {
147
151
  cart: Cart.decrementQuantity(itemId),
148
152
  }),
149
- [],
150
- ],
153
+ }),
151
154
 
152
- ClickedRemoveCartItem: ({ itemId }) => [
153
- evo(model, {
155
+ ClickedRemoveCartItem: ({ itemId }) => ({
156
+ model: evo(model, {
154
157
  cart: Cart.removeItem(itemId),
155
158
  }),
156
- [],
157
- ],
159
+ }),
158
160
 
159
- ClickedClearCart: () => [
160
- evo(model, {
161
+ ClickedClearCart: () => ({
162
+ model: evo(model, {
161
163
  cart: () => [],
162
164
  }),
163
- [],
164
- ],
165
+ }),
165
166
 
166
- UpdatedDeliveryInstructions: ({ value }) => [
167
- evo(model, {
167
+ UpdatedDeliveryInstructions: ({ value }) => ({
168
+ model: evo(model, {
168
169
  deliveryInstructions: () => value,
169
170
  }),
170
- [],
171
- ],
171
+ }),
172
172
 
173
- ClickedPlaceOrder: () => [
174
- evo(model, {
173
+ ClickedPlaceOrder: () => ({
174
+ model: evo(model, {
175
175
  orderPlaced: () => true,
176
176
  cart: () => [],
177
177
  deliveryInstructions: () => '',
178
178
  }),
179
- [],
180
- ],
179
+ }),
181
180
  })
182
181
 
183
182
  // VIEW
@@ -1,5 +1,5 @@
1
1
  import { Array, Effect, Option, Schema as S } from 'effect'
2
- import { Command, Submodel } from 'foldkit'
2
+ import { Command, Submodel, type Update } from 'foldkit'
3
3
  import { defineMessageUnion } from 'foldkit/message'
4
4
  import { replaceUrl } from 'foldkit/navigation'
5
5
  import { evo } from 'foldkit/struct'
@@ -61,46 +61,39 @@ const ReplaceSearchUrl = Command.define('ReplaceSearchUrl', {
61
61
 
62
62
  // UPDATE
63
63
 
64
- type UpdateReturn = readonly [
65
- Model,
66
- ReadonlyArray<Command.Command<Message>>,
67
- Option.Option<OutMessage>,
68
- ]
69
-
70
64
  export const update = (model: Model, message: Message) =>
71
- Message.match<UpdateReturn>(message, {
72
- CompletedReplaceSearchUrl: () => [model, [], Option.none()],
65
+ Message.match<Update.ReturnWithOutMessage<Model, Message, OutMessage>>(
66
+ message,
67
+ {
68
+ CompletedReplaceSearchUrl: () => ({ model }),
73
69
 
74
- ChangedSearchInput: ({ value }) => [
75
- evo(model, { searchText: () => value }),
76
- [
77
- ReplaceSearchUrl({
78
- url: productsRouter({
79
- searchText: Option.fromNullishOr(value || null),
70
+ ChangedSearchInput: ({ value }) => ({
71
+ model: evo(model, { searchText: () => value }),
72
+ commands: [
73
+ ReplaceSearchUrl({
74
+ url: productsRouter({
75
+ searchText: Option.fromNullishOr(value || null),
76
+ }),
80
77
  }),
81
- }),
82
- ],
83
- Option.none(),
84
- ],
78
+ ],
79
+ }),
85
80
 
86
- ClickedAddToCart: ({ item }) => [
87
- model,
88
- [],
89
- Option.some(OutMessage.AddedToCart({ item })),
90
- ],
81
+ ClickedAddToCart: ({ item }) => ({
82
+ model,
83
+ outMessage: OutMessage.AddedToCart({ item }),
84
+ }),
91
85
 
92
- ClickedIncrementQuantity: ({ itemId }) => [
93
- model,
94
- [],
95
- Option.some(OutMessage.IncrementedQuantity({ itemId })),
96
- ],
86
+ ClickedIncrementQuantity: ({ itemId }) => ({
87
+ model,
88
+ outMessage: OutMessage.IncrementedQuantity({ itemId }),
89
+ }),
97
90
 
98
- ClickedDecrementQuantity: ({ itemId }) => [
99
- model,
100
- [],
101
- Option.some(OutMessage.DecrementedQuantity({ itemId })),
102
- ],
103
- })
91
+ ClickedDecrementQuantity: ({ itemId }) => ({
92
+ model,
93
+ outMessage: OutMessage.DecrementedQuantity({ itemId }),
94
+ }),
95
+ },
96
+ )
104
97
 
105
98
  // VIEW
106
99