foldkit-crud 0.9.0 → 0.10.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.
package/README.md CHANGED
@@ -388,6 +388,12 @@ const RemoveForm = Page.children.remover
388
388
 
389
389
  - `RemoveForm.helpers.ask(id)` asks; nothing is deleted yet. `dismiss()`
390
390
  clears it.
391
+ - A no takes the question, and the button pressed, away. Give the remover
392
+ `{ returnFocus: '#delete' }` as its third argument (the selector of what
393
+ asked), or a function of the id asked about (`id => '#delete-' + id`, for
394
+ a button per row), and a no focuses it again; without it focus falls to the
395
+ page. Where
396
+ focus goes after a delete is the page's: the row that asked may be gone.
391
397
  - `Remover.Message.Confirmed()` and `Cancelled()` are the yes and the no. A
392
398
  yes with nothing asked does nothing.
393
399
  - `PostRemover.status(model)` is `Idle`, `Confirming`, `Deleting`, `Deleted`
@@ -399,6 +405,36 @@ names no list. Remote then knows the record absent: it leaves every list and
399
405
  relation it was in with no refetch, a detail of it reads `NotFound`, and an
400
406
  editor open on it reports `NotFound`.
401
407
 
408
+ ## A screen's Messages
409
+
410
+ A page with an editor and a remover answers the same four Messages every time:
411
+ open a row, start a new one, close, and ask to delete. `Crud.screen` states them
412
+ once, named after the screen, and answers them through the placements' helpers:
413
+
414
+ ```ts
415
+ const PostScreen = Crud.screen('Post', { id: Schema.String })
416
+ // In the page: Bundle.withMessages({ ...PostScreen.messages }), beside its own:
417
+ // OpenedPost({ id }), CreatedPost(), ClosedPost(), AskedToRemovePost({ id }).
418
+
419
+ const update = PostScreen.update(
420
+ { editor: PostEditor, editing: Placed, removing: RemoveForm },
421
+ placements.update((model, message) =>
422
+ Remote.reduces(message) ? { model: Data.reduce(model, message) } : { model },
423
+ ),
424
+ )
425
+ ```
426
+
427
+ `update` answers `OpenedPost` with `Placed.helpers.open(id)`, `CreatedPost` with
428
+ `blank()`, `ClosedPost` with `close()` and `AskedToRemovePost` with
429
+ `RemoveForm.helpers.ask(id)`; every other Message goes to your `update`, and
430
+ `PostEditor.after` runs after each. Leave `removing` out for a screen that cannot
431
+ delete. Given `pickers` (from `Crud.options`), it also answers a picker's
432
+ `AskedForMore` with `pickers.ask`, read out of the page's Message through the
433
+ editor placement's link, and the form still hears it (a page that also
434
+ answers `AskedForMore` itself would show two pages more: do one or the other). The screen places nothing and holds no state: the children, their `onOut`,
435
+ the query's input and the wiring stay the page's, and a page that writes these
436
+ cases itself works the same.
437
+
402
438
  ## Detail
403
439
 
404
440
  One record through a Selection, for a page that shows it. Like a list it holds
@@ -524,6 +560,24 @@ in it. With `chosen`, those rows stay among the choices, first, and
524
560
  named. Without it a `select` would show a post's author as blank the moment a
525
561
  search stopped finding them.
526
562
 
563
+ A list shows one page at a time (`pageSize`, 25 by default), so a picker can
564
+ offer fewer rows than there are. `pickers.more(model)` names the pickers whose
565
+ list has another page (`{ authorId: true }`), and a form view given it as `more`
566
+ draws a "Show more" button under each, which sends the form's
567
+ `AskedForMore({ key })`. The form changes nothing for it: the list is the
568
+ page's, and `pickers.ask(model, key)` is the page with one more page of that
569
+ list, for the read entry to fetch. `Crud.screen` answers it given the pickers:
570
+
571
+ ```ts
572
+ Placed.view(model, h, { options: pickers(model), more: pickers.more(model) })
573
+ PostScreen.update(
574
+ { editor: PostEditor, editing: Placed, pickers },
575
+ placements.update((model, message) =>
576
+ Remote.reduces(message) ? { model: Data.reduce(model, message) } : { model },
577
+ ),
578
+ )
579
+ ```
580
+
527
581
  ## Sorting a list
528
582
 
529
583
  How a list is sorted is your state and the query's input. `Sort` writes that
package/dist/index.d.mts CHANGED
@@ -5,10 +5,9 @@ import { FormControl, Submitted } from "foldkit-form";
5
5
  import { Metadata } from "foldkit-metadata";
6
6
  import { MutationDescriptor, MutationStatus, Page, QueryDescriptor, RemoteClient, RemoteData, RemoteError, RemoteMessage } from "foldkit-remote";
7
7
  import { ActiveSurface, ModelRef, Projection } from "foldkit-surface";
8
- import * as Submodel from "foldkit/submodel";
9
8
  import { Command } from "foldkit/command";
9
+ import * as Submodel from "foldkit/submodel";
10
10
  import * as Update from "foldkit/update";
11
- //#region src/display.d.ts
12
11
  /**
13
12
  * How one selected member shows: the primitive every kind of Display is a value
14
13
  * of. `Display.text()` and `Display.flag()` are values of it, made by
@@ -110,8 +109,76 @@ export declare const Display: {
110
109
  */
111
110
  show: typeof show;
112
111
  };
113
- //#endregion
114
- //#region src/sort.d.ts
112
+ /** The four Messages a screen answers, named after it: `OpenedPost`, `CreatedPost`, … */
113
+ type ScreenCases<Name extends string, Id extends Schema.Top> = { readonly [K in `Opened${Name}`]: {
114
+ readonly id: Id;
115
+ }; } & { readonly [K in `Created${Name}`]: {}; } & { readonly [K in `Closed${Name}`]: {}; } & { readonly [K in `AskedToRemove${Name}`]: {
116
+ readonly id: Id;
117
+ }; };
118
+ /**
119
+ * What the screen asks of the page's placed pieces. `Out` and `Asked` are what
120
+ * the editor's and the remover's helpers return, the page's own Model and
121
+ * Commands, which the screen hands back.
122
+ */
123
+ interface ScreenPieces<Root, Id, Out extends {
124
+ readonly model: Root;
125
+ }, Asked extends {
126
+ readonly model: Root;
127
+ }> {
128
+ /** The placed editor (`Editor.at(...)`), whose sync runs after every Message. */
129
+ readonly editor: {
130
+ readonly after: <M, R extends {
131
+ readonly model: Root;
132
+ }>(update: (model: Root, message: M) => R) => (model: Root, message: M) => R;
133
+ };
134
+ /**
135
+ * The editor's placement (`Page.children.editor`): its `open`, `blank` and
136
+ * `close`, and its link, which reads the form's Messages out of the page's.
137
+ */
138
+ readonly editing: {
139
+ readonly helpers: {
140
+ readonly open: (id: Id) => (model: Root) => Out;
141
+ readonly blank: () => (model: Root) => Out;
142
+ readonly close: () => (model: Root) => Out;
143
+ };
144
+ readonly link: {
145
+ readonly fromParentMessage: (message: never) => Option.Option<unknown>;
146
+ };
147
+ };
148
+ /** The remover's placement, when the screen can delete: its `ask`. */
149
+ readonly removing?: {
150
+ readonly helpers: {
151
+ readonly ask: (id: Id) => (model: Root) => Asked;
152
+ };
153
+ } | undefined;
154
+ /**
155
+ * The form's pickers (`Crud.options`), when they page: a picker's
156
+ * `AskedForMore` shows one more page of its list, and still reaches the form.
157
+ */
158
+ readonly pickers?: {
159
+ readonly ask: (root: NoInfer<Root>, key: never) => NoInfer<Root>;
160
+ } | undefined;
161
+ }
162
+ declare const screen: <const Name extends string, Id extends Schema.Top>(name: Name, config: {
163
+ readonly id: Id;
164
+ }) => {
165
+ /** For `Bundle.withMessages`, beside the page's own. */
166
+ messages: ScreenCases<Name, Id>;
167
+ /**
168
+ * Wraps the page's `update`: the screen's Messages are answered here, and
169
+ * every other goes to `update`; the editor's sync runs after each, so the
170
+ * loaded value is shown whichever Message brings it.
171
+ */
172
+ update: <Root, Message extends {
173
+ readonly _tag: string;
174
+ }, Result extends {
175
+ readonly model: Root;
176
+ }, Out extends {
177
+ readonly model: Root;
178
+ }, Asked extends {
179
+ readonly model: Root;
180
+ } = never>(pieces: ScreenPieces<Root, Id['Type'], Out, Asked>, update: (model: Root, message: Message) => Result) => (model: Root, message: Message) => Asked | Out | Result;
181
+ };
115
182
  /** How a list is sorted: by which of its orders, which way; `null` for the server's own order. */
116
183
  type SortState<Column extends string> = {
117
184
  readonly by: Column;
@@ -146,8 +213,6 @@ export declare const Sort: {
146
213
  inputs: <Message>(current: SortState<Columns[number]>, message: (next: SortState<Columns[number]>, column: Columns[number]) => Message) => { readonly [K in Columns[number]]: SortedColumn<Message>; };
147
214
  };
148
215
  };
149
- //#endregion
150
- //#region src/index.d.ts
151
216
  /** The parts of a `Form.make` result an editor wraps. */
152
217
  export interface EditableForm<Name extends string, FormModel, FormMessage, Value, E extends AnyEntity, Fields extends Schema.Struct.Fields, Members, R = never> {
153
218
  readonly bundle: {
@@ -293,6 +358,12 @@ export declare const Crud: {
293
358
  refresh: (root: Root) => Root;
294
359
  };
295
360
  };
361
+ /**
362
+ * A screen's Messages (`OpenedPost`, `CreatedPost`, `ClosedPost`,
363
+ * `AskedToRemovePost`) and an `update` that answers them through the editor's
364
+ * and the remover's placements, for a page that places both itself.
365
+ */
366
+ screen: typeof screen;
296
367
  /**
297
368
  * Deleting through one mutation, with a yes in between: asked, confirmed,
298
369
  * deleted. What the mutation's input is for an id is `input`'s to say, and the
@@ -308,9 +379,19 @@ export declare const Crud: {
308
379
  * input is: `id: 'id'` for `{ id }`. The id's type is that key's.
309
380
  */
310
381
  readonly id: Key & (Input extends Readonly<Record<Key, string>> ? Key : never);
382
+ }, options?: {
383
+ /**
384
+ * Where focus goes after a no takes the question away: the selector of the
385
+ * button that asked (`'#delete'`), or one for the id asked about, for a
386
+ * list with a button per row (`id => '#delete-' + id`). Without it focus
387
+ * falls to the page.
388
+ */
389
+ readonly returnFocus?: string | ((id: string) => string) | undefined;
311
390
  }) => {
312
391
  bundle: Bundle.Bundle<Name, void, RemoverModel, {
313
392
  readonly _tag: "Cancelled";
393
+ } | {
394
+ readonly _tag: "CompletedFocus";
314
395
  } | {
315
396
  readonly _tag: "Confirmed";
316
397
  }, ConfirmedRemoval, never, never, void, {}, {
@@ -329,6 +410,8 @@ export declare const Crud: {
329
410
  Message: import("foldkit/message").MessageUnion<{
330
411
  readonly Confirmed: {};
331
412
  readonly Cancelled: {};
413
+ /** Focus went back to what asked, after a no took the question away. */
414
+ readonly CompletedFocus: {};
332
415
  }>;
333
416
  at: <Root>(where: {
334
417
  readonly data: DomainLike<Root>;
@@ -383,6 +466,8 @@ export declare const Crud: {
383
466
  * read entry to fetch; none when it shows every row there is.
384
467
  */
385
468
  more: (root: Root) => Option.Option<Root>;
469
+ /** Whether the page read says more rows follow it: what a picker's "more" offers. */
470
+ hasMore: (root: Root) => boolean;
386
471
  /**
387
472
  * Asks for the list again: `Data.refresh` over its page and rows, for a
388
473
  * retry button. A failed read is not retried on its own. Unchanged while
@@ -444,9 +529,19 @@ export declare const Crud: {
444
529
  readonly choices: (root: Root) => ReadonlyArray<Choice>;
445
530
  readonly row: (id: string) => Projection<Root, RemoteData<any>>;
446
531
  readonly choiceOf: (root: Root, id: string) => Choice | undefined;
532
+ readonly more: (root: Root) => Option.Option<Root>;
533
+ readonly hasMore: (root: Root) => boolean;
447
534
  }>, options?: {
448
535
  readonly chosen?: (root: Root) => FormModel | undefined;
449
536
  }) => ((root: Root) => { readonly [K in Key]?: ReadonlyArray<Choice>; }) & {
537
+ /** For `FormView`'s `more`: the pickers whose list has more rows than it shows. */
538
+ more: (root: Root) => { readonly [K in Key]?: boolean; };
539
+ /**
540
+ * The page with one more page of `key`'s list, for the form's `AskedForMore`
541
+ * (`Crud.screen` answers it given these pickers); the page as it is for a key
542
+ * that is no picker here, or a list that shows every row.
543
+ */
544
+ ask: (root: Root, key: Key) => Root;
450
545
  /** For `Data.subscriptions`: what the pickers hold is read, so it can be named. */
451
546
  active: {
452
547
  name: string;
@@ -578,6 +673,5 @@ export declare const Crud: {
578
673
  };
579
674
  };
580
675
  };
581
- //#endregion
582
676
  export type { DisplayColumn, DisplayKind, DisplayWords, SortState, SortedColumn };
583
677
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/display.ts","../src/sort.ts","../src/index.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/display.ts","../src/screen.ts","../src/sort.ts","../src/index.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,13 +1,15 @@
1
- import { Match, Option, Schema, SchemaAST } from "effect";
1
+ import { Effect, Match, Option, Schema, SchemaAST } from "effect";
2
2
  import { Bundle } from "foldkit-bundle";
3
3
  import { Entity, SchemaShape, SelectionPageTypeId, Words } from "foldkit-entity";
4
4
  import { Form, Input } from "foldkit-form";
5
5
  import { Metadata } from "foldkit-metadata";
6
6
  import { Refusal, RemoteData } from "foldkit-remote";
7
7
  import { Projection } from "foldkit-surface";
8
+ import { define } from "foldkit/command";
9
+ import * as Dom from "foldkit/dom";
8
10
  import { defineMessageUnion } from "foldkit/message";
9
11
  import * as Submodel from "foldkit/submodel";
10
- //#region src/display.ts
12
+ // src/display.ts
11
13
  /**
12
14
  * Displays: how one selected member shows in a list or a detail, described
13
15
  * without a renderer. It is `foldkit-form`'s `Input` from the reading side: the
@@ -112,8 +114,55 @@ const Display = {
112
114
  */
113
115
  show
114
116
  };
115
- //#endregion
116
- //#region src/sort.ts
117
+ // src/screen.ts
118
+ /**
119
+ * A CRUD screen's Messages and their handling, for a page that places a
120
+ * `Crud.editor` and a `Crud.remover` itself: opening a row, starting a new one,
121
+ * closing, and asking to delete, each answered through the placements' own
122
+ * helpers, and the editor's sync run after every Message. It places nothing and
123
+ * owns no Model: the children, their `onOut`, the query's input and the wiring
124
+ * stay the page's, so the pieces are as usable without it as with it.
125
+ */
126
+ const isAskedForMore = Schema.is(Schema.TaggedStruct("AskedForMore", { key: Schema.String }));
127
+ const screen = (name, config) => {
128
+ const opened = Schema.TaggedStruct(`Opened${name}`, { id: config.id });
129
+ const created = Schema.TaggedStruct(`Created${name}`, {});
130
+ const closed = Schema.TaggedStruct(`Closed${name}`, {});
131
+ const asked = Schema.TaggedStruct(`AskedToRemove${name}`, { id: config.id });
132
+ const isOpened = Schema.is(opened);
133
+ const isCreated = Schema.is(created);
134
+ const isClosed = Schema.is(closed);
135
+ const isAsked = Schema.is(asked);
136
+ const idOf = (message) => message.id;
137
+ return {
138
+ /** For `Bundle.withMessages`, beside the page's own. */
139
+ messages: {
140
+ [`Opened${name}`]: { id: config.id },
141
+ [`Created${name}`]: {},
142
+ [`Closed${name}`]: {},
143
+ [`AskedToRemove${name}`]: { id: config.id }
144
+ },
145
+ /**
146
+ * Wraps the page's `update`: the screen's Messages are answered here, and
147
+ * every other goes to `update`; the editor's sync runs after each, so the
148
+ * loaded value is shown whichever Message brings it.
149
+ */
150
+ update: (pieces, update) => pieces.editor.after((root, received) => {
151
+ if (isOpened(received)) return pieces.editing.helpers.open(idOf(received))(root);
152
+ if (isCreated(received)) return pieces.editing.helpers.blank()(root);
153
+ if (isClosed(received)) return pieces.editing.helpers.close()(root);
154
+ if (isAsked(received) && pieces.removing !== void 0) return pieces.removing.helpers.ask(idOf(received))(root);
155
+ const { pickers } = pieces;
156
+ if (pickers === void 0) return update(root, received);
157
+ const asked = Option.filter(pieces.editing.link.fromParentMessage(received), isAskedForMore);
158
+ return update(Option.match(asked, {
159
+ onNone: () => root,
160
+ onSome: ({ key }) => pickers.ask(root, key)
161
+ }), received);
162
+ })
163
+ };
164
+ };
165
+ // src/sort.ts
117
166
  /**
118
167
  * Sorting a list, written once. How a list is sorted is the application's state
119
168
  * and its query's input, so this holds none of it: it is the schema of that
@@ -159,8 +208,7 @@ make: (columns) => {
159
208
  }]))
160
209
  };
161
210
  } };
162
- //#endregion
163
- //#region src/index.ts
211
+ // src/index.ts
164
212
  /**
165
213
  * `foldkit-crud` — management screens assembled from parts an application
166
214
  * already has.
@@ -196,12 +244,19 @@ const columnsOf = (selection) => {
196
244
  };
197
245
  const RemoverMessage = defineMessageUnion({
198
246
  Confirmed: {},
199
- Cancelled: {}
247
+ Cancelled: {},
248
+ /** Focus went back to what asked, after a no took the question away. */
249
+ CompletedFocus: {}
200
250
  });
201
251
  const idle = {
202
252
  target: null,
203
253
  requestId: null
204
254
  };
255
+ const FocusAsker = define("Crud.remover.Focus", {
256
+ args: { selector: Schema.String },
257
+ messages: [RemoverMessage.CompletedFocus],
258
+ execute: ({ selector }) => Dom.focus(selector).pipe(Effect.ignore, Effect.as(RemoverMessage.CompletedFocus()))
259
+ });
205
260
  const Crud = {
206
261
  /**
207
262
  * One Entity read through a Selection, for a page that shows it. Like a list
@@ -236,11 +291,17 @@ const Crud = {
236
291
  }
237
292
  }),
238
293
  /**
294
+ * A screen's Messages (`OpenedPost`, `CreatedPost`, `ClosedPost`,
295
+ * `AskedToRemovePost`) and an `update` that answers them through the editor's
296
+ * and the remover's placements, for a page that places both itself.
297
+ */
298
+ screen,
299
+ /**
239
300
  * Deleting through one mutation, with a yes in between: asked, confirmed,
240
301
  * deleted. What the mutation's input is for an id is `input`'s to say, and the
241
302
  * id it names there (`(id: PostId) => ...`) is the id `ask` takes.
242
303
  */
243
- remover: (name, config) => {
304
+ remover: (name, config, options = {}) => {
244
305
  const inputFor = (id) => "input" in config ? config.input(id) : { [config.id]: id };
245
306
  return {
246
307
  bundle: Bundle.make(name, {
@@ -250,13 +311,25 @@ const Crud = {
250
311
  }),
251
312
  Message: RemoverMessage,
252
313
  init: () => ({ model: idle }),
253
- update: (model, message) => message._tag === "Cancelled" || model.target === null ? { model: idle } : {
254
- model,
255
- outMessage: {
256
- _tag: "Confirmed",
257
- id: model.target
258
- }
259
- },
314
+ update: (model, message) => RemoverMessage.match(message, {
315
+ Cancelled: () => {
316
+ const { returnFocus } = options;
317
+ if (model.target === null || returnFocus === void 0) return { model: idle };
318
+ const selector = typeof returnFocus === "string" ? returnFocus : returnFocus(model.target);
319
+ return {
320
+ model: idle,
321
+ commands: [FocusAsker({ selector })]
322
+ };
323
+ },
324
+ Confirmed: () => model.target === null ? { model: idle } : {
325
+ model,
326
+ outMessage: {
327
+ _tag: "Confirmed",
328
+ id: model.target
329
+ }
330
+ },
331
+ CompletedFocus: () => ({ model })
332
+ }),
260
333
  helpers: {
261
334
  /** Asks whether to delete `id`. Nothing is deleted until `Confirmed`. */
262
335
  ask: (_, id) => ({ model: {
@@ -338,6 +411,11 @@ const Crud = {
338
411
  * read entry to fetch; none when it shows every row there is.
339
412
  */
340
413
  more: (root) => Option.flatMap(projectionOf(root), (projection) => data.more(root, projection)),
414
+ /** Whether the page read says more rows follow it: what a picker's "more" offers. */
415
+ hasMore: (root) => {
416
+ const read = page(root);
417
+ return (read._tag === "Ready" || read._tag === "Refreshing") && read.value.hasNext;
418
+ },
341
419
  /**
342
420
  * Asks for the list again: `Data.refresh` over its page and rows, for a
343
421
  * retry button. A failed read is not retried on its own. Unchanged while
@@ -437,17 +515,27 @@ const Crud = {
437
515
  };
438
516
  const [first] = lists;
439
517
  if (first === void 0) throw new Error("Crud.options: give it the lists its pickers pick from; with none, it has nothing to read");
440
- return Object.assign(choices, {
441
- /** For `Data.subscriptions`: what the pickers hold is read, so it can be named. */
442
- active: {
443
- name: `${pickers.map(([key]) => key).join("+")} chosen`,
444
- owner: first.owner,
445
- projectionOf: (root) => {
446
- const rows = held(root);
447
- return rows.length === 0 ? Option.none() : Option.some(Projection.struct(Object.fromEntries(rows.map(([key, list, id]) => [`${key}:${id}`, list.row(id)]))));
448
- },
449
- messages: []
450
- } });
518
+ const listOf = new Map(pickers);
519
+ return Object.assign(choices, {
520
+ /** For `FormView`'s `more`: the pickers whose list has more rows than it shows. */
521
+ more: (root) => Object.fromEntries(pickers.flatMap(([key, list]) => list.hasMore(root) ? [[key, true]] : [])),
522
+ /**
523
+ * The page with one more page of `key`'s list, for the form's `AskedForMore`
524
+ * (`Crud.screen` answers it given these pickers); the page as it is for a key
525
+ * that is no picker here, or a list that shows every row.
526
+ */
527
+ ask: (root, key) => Option.getOrElse(Option.flatMap(Option.fromNullishOr(listOf.get(key)), (list) => list.more(root)), () => root),
528
+ /** For `Data.subscriptions`: what the pickers hold is read, so it can be named. */
529
+ active: {
530
+ name: `${pickers.map(([key]) => key).join("+")} chosen`,
531
+ owner: first.owner,
532
+ projectionOf: (root) => {
533
+ const rows = held(root);
534
+ return rows.length === 0 ? Option.none() : Option.some(Projection.struct(Object.fromEntries(rows.map(([key, list, id]) => [`${key}:${id}`, list.row(id)]))));
535
+ },
536
+ messages: []
537
+ }
538
+ });
451
539
  },
452
540
  /**
453
541
  * What the pieces on a page require of Remote, by the names given: a placed
@@ -786,7 +874,6 @@ active: {
786
874
  };
787
875
  }
788
876
  };
789
- //#endregion
790
877
  export { Crud, Display, Sort };
791
878
 
792
879
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/display.ts","../src/sort.ts","../src/index.ts"],"sourcesContent":["/**\n * Displays: how one selected member shows in a list or a detail, described\n * without a renderer. It is `foldkit-form`'s `Input` from the reading side: the\n * member says what it is, a Display says how it reads, and which DOM draws it is\n * the application's.\n */\nimport { Schema, SchemaAST } from 'effect'\nimport { SchemaShape, type AnyEntity, type EntityMember } from 'foldkit-entity'\nimport { Metadata } from 'foldkit-metadata'\n\n/**\n * How one selected member shows: the primitive every kind of Display is a value\n * of. `Display.text()` and `Display.flag()` are values of it, made by\n * `Display.kind`, which is what an application calls for a badge or a relative\n * date. The kinds here are a collection, not a special case.\n */\nexport interface Display<Data = unknown> {\n /** The name a renderer is found by. */\n readonly kind: string\n /** Whether a view draws it. A member read and not shown, usually the id, is `false`. */\n readonly shown: boolean\n /** What this kind needs: a format, a relation's target. */\n readonly data: Data\n /**\n * The value as text: what a cell says when nothing draws it specially. It is\n * never given `null` or `undefined`; `Display.show` says `nothing` for those.\n */\n readonly text: (value: unknown, words: DisplayWords) => string\n}\n\n/** A kind of Display: how its values are made, and how one is told from another. */\nexport interface DisplayKind<Data> {\n readonly kind: string\n readonly of: (data: Data) => Display<Data>\n readonly is: (display: Display) => display is Display<Data>\n}\n\n/** A selected member with its words and its Display. A list's column, a detail's line. */\nexport interface DisplayColumn<Key extends string = string> {\n readonly key: Key\n readonly label: string\n readonly member: EntityMember\n readonly display: Display\n}\n\n/** The words a Display needs that are not the value's own. */\nexport interface DisplayWords {\n /** Default `yes`. */\n readonly yes?: string\n /** Default `no`. */\n readonly no?: string\n /** What stands for nothing: `null`, `undefined`, an empty list. Default the empty string. */\n readonly nothing?: string\n /** Between the items of a list, and between the members of a nested value. Default `, `. */\n readonly separator?: string\n}\n\nconst key = Metadata.key<Display>('foldkit-crud/display', {\n // The last Display attached wins: a later annotation refines an earlier one.\n merge: displays => displays.slice(-1),\n summarize: display => display.kind,\n})\n\nconst fromSchema = (schema: Schema.Top): Display => {\n const members = SchemaShape.present(Schema.toType(schema).ast)\n const [only] = members\n if (members.length === 1 && only !== undefined) {\n if (SchemaAST.isNumber(only)) return Number_.of({})\n if (SchemaAST.isBoolean(only)) return Flag.of(nothing)\n }\n return Text.of({})\n}\n\n/** A kind of Display. The kinds below are made with it, and so is an application's. */\nconst kind = <Data = Record<string, never>>(\n name: string,\n spec: {\n readonly shown?: boolean\n readonly text: (data: Data, value: unknown, words: DisplayWords) => string\n },\n): DisplayKind<Data> => ({\n kind: name,\n of: data =>\n Object.freeze({\n kind: name,\n shown: spec.shown ?? true,\n data,\n text: (value: unknown, words: DisplayWords) => spec.text(data, value, words),\n }),\n is: (display): display is Display<Data> => display.kind === name,\n})\n\nconst show = (display: Display, value: unknown, words: DisplayWords = {}): string =>\n value === null || value === undefined ? (words.nothing ?? '') : display.text(value, words)\n\nconst Text = kind<{ readonly format?: ((value: unknown) => string) | undefined }>('Text', {\n text: (data, value) => (data.format === undefined ? String(value) : data.format(value)),\n})\nconst Number_ = kind<{ readonly format?: ((value: number) => string) | undefined }>('Number', {\n text: (data, value) =>\n typeof value === 'number' && data.format !== undefined ? data.format(value) : String(value),\n})\nconst Flag = kind('Flag', {\n text: (_, value, words) => (value === true ? (words.yes ?? 'yes') : (words.no ?? 'no')),\n})\nconst Hidden = kind('Hidden', { shown: false, text: () => '' })\n/** A relation read as refs: which one, with nothing to say about it but its id. */\nconst Ref = kind<{ readonly target: AnyEntity; readonly many: boolean }>('Ref', {\n text: (_, value, words) => {\n const ids = (Array.isArray(value) ? value : [value]).map(ref =>\n typeof ref === 'object' && ref !== null && 'id' in ref ? String(ref.id) : String(ref),\n )\n return ids.length === 0 ? (words.nothing ?? '') : ids.join(words.separator ?? ', ')\n },\n})\n/**\n * A relation read through a Selection: the target's own members, each with a\n * Display. `shape` is how many there are: one, a list, or a page of a list.\n */\nconst Nested = kind<{\n readonly target: AnyEntity\n readonly shape: 'one' | 'many' | 'page'\n readonly columns: ReadonlyArray<DisplayColumn>\n}>('Nested', {\n text: (data, value, words) => {\n const one = (item: unknown): string =>\n data.columns\n .filter(column => column.display.shown)\n .map(column =>\n show(\n column.display,\n (item as Readonly<Record<string, unknown>> | null)?.[column.key],\n words,\n ),\n )\n .filter(text => text !== '')\n .join(' ')\n const items =\n data.shape === 'one'\n ? [value]\n : data.shape === 'page'\n ? ((value as { readonly items?: ReadonlyArray<unknown> }).items ?? [])\n : (value as ReadonlyArray<unknown>)\n return items.length === 0 ? (words.nothing ?? '') : items.map(one).join(words.separator ?? ', ')\n },\n})\n\nconst nothing: Record<string, never> = Object.freeze({})\n\nexport const Display = {\n /**\n * A kind of Display. `Display.kind<{ tone: string }>('Badge', { text: (_, value) => String(value) })`\n * gives `.of(data)` to make one and `.is(display)` to tell one, exactly as the\n * kinds below were made. `text` is the floor; a view draws it specially once\n * it is given a renderer for `Badge`.\n */\n kind,\n\n // The kinds this package resolves to. A view finds its renderer by `kind`.\n Text,\n Number: Number_,\n Flag,\n Hidden,\n Ref,\n Nested,\n\n text: (format?: (value: unknown) => string): Display => Text.of({ format }),\n number: (format?: (value: number) => string): Display => Number_.of({ format }),\n flag: (): Display => Flag.of(nothing),\n /** Read, so a row can be opened or keyed by it, and not shown: usually the id. */\n hidden: (): Display => Hidden.of(nothing),\n\n /**\n * Entity metadata: how a member shows wherever it is listed, e.g.\n * `Entity.annotateMembers({ price: Display.of(Display.number(cents => ...)) })`.\n */\n of: (display: Display) => key.of(display),\n\n /**\n * The Display of one selected member, from the most to the least explicit\n * source: the member's `Display.of` metadata, how the relation was selected,\n * then the shape of its schema. `nested` is the columns of a nested Selection,\n * with `shape` saying how many the relation yields.\n */\n resolve: (\n member: EntityMember,\n nested?: {\n readonly shape: 'one' | 'many' | 'page'\n readonly columns: ReadonlyArray<DisplayColumn>\n },\n ): Display => {\n const [explicit] = key.get(member.metadata)\n if (explicit !== undefined) return explicit\n if (member._tag !== 'Relation') return fromSchema(member.schema as Schema.Top)\n const target = member.target()\n return nested === undefined\n ? Ref.of({ target, many: member.cardinality === 'many' })\n : Nested.of({ target, shape: nested.shape, columns: nested.columns })\n },\n\n /**\n * A value as the text its Display calls for: what a cell says when nothing\n * draws it specially. A view that wants a link or a badge reads the Display\n * and the value itself; this is the floor every view can stand on.\n */\n show,\n}\n","/**\n * Sorting a list, written once. How a list is sorted is the application's state\n * and its query's input, so this holds none of it: it is the schema of that\n * state, what a click on a header makes of it, and the shape a drawn table\n * takes. The server reads the same state and decides which columns it means.\n */\nimport { Schema } from 'effect'\n\n/** How a list is sorted: by which of its orders, which way; `null` for the server's own order. */\nexport type SortState<Column extends string> = {\n readonly by: Column\n readonly direction: 'asc' | 'desc'\n} | null\n\n/** How one column is sorted now, and the Message a click on its header sends. */\nexport interface SortedColumn<Message> {\n readonly direction?: 'asc' | 'desc' | undefined\n readonly message: Message\n}\n\nexport const Sort = {\n /**\n * The orders a list offers, by name. A name is an order the server knows, not\n * a column: which columns may sort, and what `title` means in SQL, stay the\n * server's to decide.\n */\n make: <const Columns extends readonly [string, ...string[]]>(columns: Columns) => {\n type Column = Columns[number]\n type State = SortState<Column>\n /** A click on a header: ascending, then descending, then the server's own order again. */\n const toggle = (current: State, column: Column): State =>\n current === null || current.by !== column\n ? { by: column, direction: 'asc' }\n : current.direction === 'asc'\n ? { by: column, direction: 'desc' }\n : null\n return {\n columns,\n /** For the Model field, and for the query's input. */\n Schema: Schema.NullOr(\n Schema.Struct({\n by: Schema.Literals(columns),\n direction: Schema.Literals(['asc', 'desc']),\n }),\n ) as unknown as Schema.Codec<State, State>,\n /** The server's own order. */\n none: null as State,\n toggle,\n /**\n * What a drawn table takes as `sort`: every order with how it is sorted now\n * and the Message a click sends, made from the state the click leads to and\n * the column clicked. A click meets the state last drawn, so two clicks\n * inside one frame both send the same `next`; send the column instead\n * (`(_, column) => Sorted({ column })`) and `toggle` it in `update`, from\n * the Model as it is then.\n */\n inputs: <Message>(\n current: State,\n message: (next: State, column: Column) => Message,\n ): { readonly [K in Column]: SortedColumn<Message> } =>\n Object.fromEntries(\n columns.map(column => [\n column,\n {\n direction: current?.by === column ? current.direction : undefined,\n message: message(toggle(current, column), column),\n },\n ]),\n ) as { readonly [K in Column]: SortedColumn<Message> },\n }\n },\n}\n","/**\n * `foldkit-crud` — management screens assembled from parts an application\n * already has.\n *\n * An editor joins a `foldkit-form` form, the Remote mutation its value feeds,\n * and the Entity they share: it loads what the form writes, shows it, turns a\n * valid submit into the mutation, and says how that went. Everything it makes is\n * ordinary Foldkit: a Bundle, an ActiveSurface, Update Steps. Nothing is\n * generated from an Entity alone; each capability is declared.\n */\nimport { Match, Option, Schema } from 'effect'\nimport { Bundle } from 'foldkit-bundle'\nimport {\n Entity,\n SelectionPageTypeId,\n Words,\n type AnyEntity,\n type EntityInput,\n type EntityMember,\n type InputMember,\n type Selection,\n type IdOf,\n type SelectionPage,\n} from 'foldkit-entity'\nimport { Form, Input, type FormControl, type Submitted } from 'foldkit-form'\nimport { Display, type DisplayColumn } from './display.js'\nimport {\n Refusal,\n RemoteData,\n type MutationDescriptor,\n type Page,\n type QueryDescriptor,\n type MutationStatus,\n type RemoteClient,\n type RemoteError,\n type RemoteMessage,\n} from 'foldkit-remote'\nimport { Projection, type ActiveSurface, type ModelRef } from 'foldkit-surface'\nimport type { Command } from 'foldkit/command'\nimport { defineMessageUnion } from 'foldkit/message'\nimport type { Html, HtmlBuilder } from 'foldkit/html'\nimport * as Submodel from 'foldkit/submodel'\nimport type * as Update from 'foldkit/update'\n\n/** The parts of a `Form.make` result an editor wraps. */\nexport interface EditableForm<\n Name extends string,\n FormModel,\n FormMessage,\n Value,\n E extends AnyEntity,\n Fields extends Schema.Struct.Fields,\n Members,\n R = never,\n> {\n readonly bundle: {\n readonly name: Name\n readonly Model: Schema.Codec<FormModel, unknown>\n readonly Message: Schema.Codec<FormMessage, unknown>\n readonly update: (\n model: FormModel,\n message: FormMessage,\n args: void,\n ) => Update.ReturnWithOutMessage<FormModel, FormMessage, Submitted<Value>, R>\n }\n readonly input: EntityInput<E, Fields, Members>\n readonly initial: FormModel\n readonly fill: (model: FormModel, values: Partial<Value>) => { readonly model: FormModel }\n readonly authoredChanged: (before: FormModel, after: FormModel) => boolean\n readonly controls: ReadonlyArray<FormControl>\n readonly Message: {\n // Method syntax: the form's own takes only its keys, which the editor checks first.\n Refused(fields: { readonly key: string; readonly error: string }): FormMessage\n }\n}\n\n/**\n * An editor's slice of the parent Model. `target` is the id being edited, or\n * `null` for a new one; `filled` is whether the loaded value has been shown;\n * `requestId` is the save in progress or last settled.\n */\n/**\n * Which members of a row its reader may not set, read from members loaded\n * beside the form's: what `Crud.editor`'s `locks` takes. `foldkit-guard`'s\n * `May.locks(Todo)` is one, from the server's `may`.\n */\nexport interface Locks {\n /** The members to load beside the form's, by key: `{ may: true }`. */\n readonly select: Readonly<Record<string, true>>\n /** Whether `member` of the loaded row may not be set by its reader. */\n readonly locked: (row: Readonly<Record<string, unknown>>, member: string) => boolean\n}\n\nexport interface EditorModel<FormModel> {\n readonly form: FormModel\n readonly mode: 'closed' | 'new' | 'edit'\n readonly target: string | null\n readonly filled: boolean\n readonly requestId: string | null\n /** Whether the author changed the form since its last save was sent. */\n readonly edited: boolean\n /** What the last save sent, for the form to take once it lands. */\n readonly sent: Option.Option<Readonly<Record<string, unknown>>>\n /** The save whose answer the form already took, so it is taken once. */\n readonly settledFor: Option.Option<string>\n /** The save whose refusal of a field the form already shows, so it is shown once. */\n readonly refusedFor: Option.Option<string>\n /**\n * The input values the row held as the form knows it: filled from it, and\n * moved on by each save of this editor's. A save compares to it, to send\n * only what changed.\n */\n readonly filledWith: Option.Option<Readonly<Record<string, unknown>>>\n}\n\n/**\n * A remover's slice of the parent Model: the id it was asked to delete and is\n * waiting on a yes for, and the delete in progress or last settled.\n */\nexport interface RemoverModel {\n readonly target: string | null\n readonly requestId: string | null\n}\n\n/** A remover's out Message: the user said yes to deleting `id`. */\nexport interface ConfirmedRemoval {\n readonly _tag: 'Confirmed'\n readonly id: string\n}\n\nexport type RemoverStatus =\n | 'Idle'\n /** Asked to delete an id, and waiting for a yes or a no. */\n | 'Confirming'\n | 'Deleting'\n | 'Deleted'\n | 'DeleteFailed'\n\nexport type EditorStatus =\n | 'Closed'\n /** Editing an id whose current values have not arrived. */\n | 'Loading'\n | 'NotFound'\n | 'LoadFailed'\n | 'Editing'\n | 'Saving'\n | 'Saved'\n | 'SaveFailed'\n /** The server refused the save with `Refusal.conflict`: the row moved on since it was read. */\n | 'Conflict'\n /**\n * Someone else saved the row since it was read: its revision moved past the\n * one the form holds. The draft is kept; a save of it would be a conflict.\n */\n | 'Moved'\n\n/** The parts of a bound Remote domain an editor uses. */\nexport interface DomainLike<Root> {\n // Method syntax: the descriptors are checked where the editor is made, not here.\n mutate(\n model: Root,\n mutation: any,\n input: any,\n options?: { readonly keys?: ReadonlyArray<string> | undefined },\n ): {\n readonly model: Root\n readonly requestId: string\n readonly command: Command<RemoteMessage, never, RemoteClient>\n }\n get(selection: any, id: string): Projection<Root, RemoteData<any>>\n query(query: any, input: any, options: any): Projection<Root, RemoteData<Page<any>>>\n more(model: Root, projection: any): Option.Option<Root>\n refresh(model: Root, target: any): Root\n mutation(model: Root, requestId: string): MutationStatus\n refusal(model: Root, requestId: string, mutation: any): Option.Option<unknown>\n /** `Data.active`: a read of the domain as an active Surface of its application. */\n active(\n name: string,\n projectionOf: (model: Root) => Option.Option<Projection<Root, any>>,\n ): ActiveSurface<Root>\n}\n\ntype Step<Root> = Update.Step<Root, RemoteMessage, RemoteClient>\n\n/** One thing a relation picker offers. */\nexport interface Choice {\n readonly value: string\n readonly label: string\n}\n\n/**\n * One column of a list, or one line of a detail: a member the Selection reads,\n * with its words and its `Display`.\n */\nexport type ListColumn<Key extends string = string> = DisplayColumn<Key>\n\nconst columnLabel = (key: string, member: EntityMember): string =>\n Option.getOrElse(\n member._tag === 'Relation' ? Option.none() : Words.of(member.schema as Schema.Top).title,\n () => Form.labelOf(member) ?? key,\n )\n\ntype AnySelection = Selection<string, unknown, Schema.Constraint>\n\n/** The selected members in the Selection's order, each with its label and its Display. */\nconst columnsOf = <Members>(\n selection: Selection<string, Members, Schema.Constraint>,\n): ReadonlyArray<ListColumn<keyof Members & string>> => {\n const members: Readonly<Record<string, EntityMember>> = selection.entity.members\n const selected = selection.members as Readonly<\n Record<string, true | AnySelection | SelectionPage<string, Schema.Constraint>>\n >\n return Object.keys(selected).map(key => {\n const member = members[key]!\n const how = selected[key]!\n // A relation read through a Selection shows the target's own members.\n const nested =\n how === true || member._tag !== 'Relation'\n ? undefined\n : SelectionPageTypeId in how\n ? { shape: 'page' as const, columns: columnsOf(how.selection) }\n : {\n shape: member.cardinality === 'many' ? ('many' as const) : ('one' as const),\n columns: columnsOf(how),\n }\n return {\n key: key as keyof Members & string,\n label: columnLabel(key, member),\n member,\n display: Display.resolve(member, nested),\n }\n })\n}\n\nconst RemoverMessage = defineMessageUnion({ Confirmed: {}, Cancelled: {} })\ntype RemoverMessage = typeof RemoverMessage.Type\n\nconst idle: RemoverModel = { target: null, requestId: null }\n\nexport const Crud = {\n /**\n * One Entity read through a Selection, for a page that shows it. Like a list\n * it holds no state: the value is Remote's and the id is the application's.\n */\n detail: <const Name extends string, EntityName extends string, Members, Row>(\n name: Name,\n config: {\n readonly selection: Selection<EntityName, Members, Schema.Constraint & { readonly Type: Row }>\n },\n ) => ({\n name,\n /** The selected members in the Selection's order, each with its label. */\n fields: columnsOf(config.selection),\n /** Type-only: the value shown, for a view to be typed by. Never read. */\n Value: undefined as unknown as Row,\n\n /** The detail where it lives: `id` is the one shown, none while none is. */\n at: <Root>(where: {\n readonly data: DomainLike<Root>\n readonly id: (root: Root) => Option.Option<string>\n }) => {\n const projectionOf = (root: Root) =>\n Option.map(where.id(root), id => where.data.get(config.selection, id))\n return {\n /** For `Data.subscriptions`: the value is fetched and retained while an id is shown. */\n active: where.data.active(name, projectionOf),\n value: (root: Root): RemoteData<Row> =>\n Option.match(projectionOf(root), {\n onNone: () => ({ _tag: 'Initial' }),\n onSome: projection => projection.read(root),\n }),\n /**\n * Asks for the value again: `Data.refresh` over this detail, for a retry\n * button. A failed read is not retried on its own. Unchanged while no id\n * is shown.\n */\n refresh: (root: Root): Root =>\n Option.match(projectionOf(root), {\n onNone: () => root,\n onSome: projection => where.data.refresh(root, projection),\n }),\n }\n },\n }),\n\n /**\n * Deleting through one mutation, with a yes in between: asked, confirmed,\n * deleted. What the mutation's input is for an id is `input`'s to say, and the\n * id it names there (`(id: PostId) => ...`) is the id `ask` takes.\n */\n remover: <\n const Name extends string,\n Input,\n Id extends string = string,\n const Key extends keyof Input = never,\n >(\n name: Name,\n config:\n | {\n readonly mutation: MutationDescriptor<string, Input, any>\n readonly input: (id: Id) => Input\n }\n | {\n readonly mutation: MutationDescriptor<string, Input, any>\n /**\n * The key of the mutation's input that holds the id, when that is all the\n * input is: `id: 'id'` for `{ id }`. The id's type is that key's.\n */\n readonly id: Key & (Input extends Readonly<Record<Key, string>> ? Key : never)\n },\n ) => {\n type AskedId = [Key] extends [never] ? Id : Input[Key] & string\n const inputFor = (id: string): Input =>\n 'input' in config ? config.input(id as Id) : ({ [config.id]: id } as Input)\n const bundle = Bundle.make(name, {\n Model: Schema.Struct({\n target: Schema.NullOr(Schema.String),\n requestId: Schema.NullOr(Schema.String),\n }) as unknown as Schema.Codec<RemoverModel, unknown>,\n Message: RemoverMessage,\n init: () => ({ model: idle }),\n update: (model: RemoverModel, message: RemoverMessage) =>\n message._tag === 'Cancelled' || model.target === null\n ? { model: idle }\n : { model, outMessage: { _tag: 'Confirmed', id: model.target } as ConfirmedRemoval },\n helpers: {\n /** Asks whether to delete `id`. Nothing is deleted until `Confirmed`. */\n ask: (_: RemoverModel, id: AskedId) => ({ model: { target: id, requestId: null } }),\n dismiss: () => ({ model: idle }),\n },\n })\n\n return {\n bundle,\n /** The remover's Messages, for the yes and the no of a confirmation. */\n Message: RemoverMessage,\n\n at: <Root>(where: {\n readonly data: DomainLike<Root>\n readonly model: ModelRef<Root, RemoverModel>\n }) => {\n const { data, model: slice } = where\n const outcome = (root: Root): MutationStatus => {\n const { requestId } = slice.get(root)\n return requestId === null ? { _tag: 'Unknown' } : data.mutation(root, requestId)\n }\n return {\n /** For the placement: a yes becomes the mutation, and its request is remembered. */\n onOut:\n (confirmed: ConfirmedRemoval): Step<Root> =>\n root => {\n const started = data.mutate(root, config.mutation, inputFor(confirmed.id))\n return {\n model: slice.set(started.model, {\n target: confirmed.id,\n requestId: started.requestId,\n }),\n commands: [started.command],\n }\n },\n\n /** The id being asked about or deleted, for the words of a confirmation. */\n target: (root: Root): AskedId | null => slice.get(root).target as AskedId | null,\n\n status: (root: Root): RemoverStatus => {\n const { target } = slice.get(root)\n const deleting = outcome(root)\n return deleting._tag === 'Pending'\n ? 'Deleting'\n : deleting._tag === 'Applied'\n ? 'Deleted'\n : deleting._tag === 'Failed'\n ? 'DeleteFailed'\n : target === null\n ? 'Idle'\n : 'Confirming'\n },\n\n /** Why the last delete failed, while `status` is `DeleteFailed`. */\n error: (root: Root): RemoteError | undefined => {\n const deleting = outcome(root)\n return deleting._tag === 'Failed' ? deleting.error : undefined\n },\n }\n },\n }\n },\n\n /**\n * A list over one query: which rows to show is the query's, what to show of\n * each is the Selection's. The pages live in Remote; the list holds no state.\n */\n list: <\n const Name extends string,\n EntityName extends string,\n Members,\n S extends Schema.Constraint,\n Input,\n >(\n name: Name,\n config: {\n readonly query: QueryDescriptor<string, Input, any>\n // The row is the Selection's own value, so it is typed with or without a `choice`.\n readonly selection: Selection<EntityName, Members, S>\n /** How many rows a page holds. Default 25. */\n readonly pageSize?: number\n /**\n * How a row reads as a choice in a relation picker: its id, and the words\n * that identify it to a person. Give it to a list that feeds pickers.\n */\n readonly choice?: {\n readonly value: (row: S['Type']) => string\n readonly label: (row: S['Type']) => string\n }\n },\n ) => {\n const { query, selection, pageSize = 25, choice } = config\n const columns = columnsOf(selection)\n\n return {\n name,\n /** The selected members in the Selection's order, each with its label. */\n columns,\n /** Type-only: a row, for a view to be typed by. Never read. */\n Row: undefined as unknown as S['Type'],\n\n /**\n * The list where it lives. `input` is the query's input as the Model has\n * it (filters, a search term), or `undefined` while the list is not shown.\n */\n at: <Root>(where: {\n readonly data: DomainLike<Root>\n /** The list's input from the Model; none while the list is not shown. */\n readonly input: (root: Root) => Option.Option<Input>\n }) => {\n const { data, input } = where\n const projectionOf = (root: Root) =>\n Option.map(input(root), value =>\n data.query(query, value, { select: selection, first: pageSize }),\n )\n const page = (root: Root): RemoteData<Page<S['Type']>> =>\n Option.match(projectionOf(root), {\n onNone: () => ({ _tag: 'Initial' }),\n onSome: projection => projection.read(root),\n })\n\n const active = data.active(name, projectionOf)\n\n return {\n /** For `Data.subscriptions`: the page and its rows are fetched and retained while shown. */\n active,\n\n page,\n\n /**\n * \"Load more\": the Model with the list showing one page more, for the\n * read entry to fetch; none when it shows every row there is.\n */\n more: (root: Root): Option.Option<Root> =>\n Option.flatMap(projectionOf(root), projection => data.more(root, projection)),\n\n /**\n * Asks for the list again: `Data.refresh` over its page and rows, for a\n * retry button. A failed read is not retried on its own. Unchanged while\n * the list is not shown.\n */\n refresh: (root: Root): Root =>\n Option.match(projectionOf(root), {\n onNone: () => root,\n onSome: projection => data.refresh(root, projection),\n }),\n\n name,\n /** Whose requirement the list is, for anything that requires beside it. */\n owner: active.owner,\n /**\n * One of the list's Entity through the list's Selection, by id, whether or\n * not the query finds it now: what a picker reads to name what is chosen.\n */\n row: (id: string): Projection<Root, RemoteData<any>> => data.get(selection, id),\n /** The Entity the rows are of, which is how a picker finds the list for its target. */\n entity: selection.entity as AnyEntity,\n /** Whether the list was given a `choice`, so its rows can be a picker's choices. */\n offersChoices: choice !== undefined,\n\n /**\n * The loaded rows as a picker's choices, read through `choice`. Listing a\n * relation's target is this query, which the application chose and the\n * server authorizes; nothing is read because a relation exists.\n */\n choices: (root: Root): ReadonlyArray<Choice> => {\n if (choice === undefined)\n throw new Error(`Crud list \"${name}\": give it a \"choice\" to use its rows in a picker`)\n const read = page(root)\n return read._tag === 'Ready' || read._tag === 'Refreshing'\n ? read.value.items.map(row => ({\n value: choice.value(row),\n label: choice.label(row),\n }))\n : []\n },\n\n /** That row as a choice, once it is read. */\n choiceOf: (root: Root, id: string): Choice | undefined => {\n if (choice === undefined) return undefined\n const read = data.get(selection, id).read(root)\n return read._tag === 'Ready' || read._tag === 'Refreshing'\n ? { value: choice.value(read.value), label: choice.label(read.value) }\n : undefined\n },\n }\n },\n }\n },\n\n /**\n * A form's Submodel view as the view of the editor that wraps it, for\n * `Bundle.withView`: the editor's Messages are the form's, and its Model holds\n * the form's under `form`.\n *\n * ```ts\n * Editor.bundle.pipe(Bundle.withView(Crud.editorView(FormView.submodel(form, view))))\n * ```\n */\n editorView: <FormModel, Message, ViewInputs>(\n view: Submodel.View<FormModel, Message, ViewInputs>,\n ): Submodel.View<EditorModel<FormModel>, Message, ViewInputs> =>\n Submodel.defineView<EditorModel<FormModel>, Message, ViewInputs>(((\n model: EditorModel<FormModel>,\n inputs: ViewInputs,\n h: HtmlBuilder<Message>,\n ): Html =>\n (view as (model: FormModel, inputs: ViewInputs, h: HtmlBuilder<Message>) => Html)(\n model.form,\n inputs,\n h,\n )) as never),\n\n /**\n * The choices of every relation picker in a form, from the lists of their\n * targets: `foldkit-mixins-form`'s `options`, keyed by the form's keys. A\n * picker whose target no list here is over is a wiring mistake, reported now.\n *\n * With `chosen`, the form's Model as the page has it, what a picker already\n * holds stays among its choices when the list no longer finds it (a search\n * narrowed it, or it is on a later page), and `active` requires those rows so\n * their words are there. Give `chosen` to any form whose pickers search.\n */\n options: <Key extends string, Root, FormModel = never>(\n form: {\n readonly controls: ReadonlyArray<FormControl<Key>>\n readonly field?: (model: FormModel, key: never) => { readonly value: unknown }\n },\n lists: ReadonlyArray<{\n readonly name: string\n readonly entity: AnyEntity\n readonly offersChoices: boolean\n readonly owner: object\n readonly choices: (root: Root) => ReadonlyArray<Choice>\n readonly row: (id: string) => Projection<Root, RemoteData<any>>\n readonly choiceOf: (root: Root, id: string) => Choice | undefined\n }>,\n options: { readonly chosen?: (root: Root) => FormModel | undefined } = {},\n ) => {\n const pickers = form.controls.flatMap(({ key, control }) => {\n if (!Input.RelationOne.is(control) && !Input.RelationMany.is(control)) return []\n const { target } = control.data\n const list = lists.find(candidate => Entity.same(candidate.entity, target))\n if (list === undefined)\n throw new Error(\n `Crud.options: \"${key}\" picks a ${target.name}, and no list given is over ${target.name}`,\n )\n if (!list.offersChoices)\n throw new Error(\n `Crud.options: \"${key}\" would pick from list \"${list.name}\", which has no \"choice\"`,\n )\n return [[key, list] as const]\n })\n\n /** The ids each picker holds now: the draft of a `one`, or of a `many`. */\n const held = (root: Root): ReadonlyArray<readonly [Key, (typeof lists)[number], string]> => {\n const model = options.chosen?.(root)\n const field = form.field as\n | ((model: FormModel, key: string) => { readonly value: unknown })\n | undefined\n if (model === undefined || field === undefined) return []\n return pickers.flatMap(([key, list]) => {\n const draft = field(model, key).value\n const ids = Array.isArray(draft) ? draft : typeof draft === 'string' ? [draft] : []\n return ids\n .filter((id): id is string => typeof id === 'string' && id !== '')\n .map(id => [key, list, id] as const)\n })\n }\n\n const choices = (root: Root): { readonly [K in Key]?: ReadonlyArray<Choice> } => {\n const chosen = held(root)\n return Object.fromEntries(\n pickers.map(([key, list]) => {\n const listed = list.choices(root)\n const missing = chosen\n .filter(([heldBy, , id]) => heldBy === key && !listed.some(item => item.value === id))\n .flatMap(([, , id]) => list.choiceOf(root, id) ?? [])\n // What is chosen leads, so it is in reach whatever the list shows.\n return [key, [...missing, ...listed]]\n }),\n ) as unknown as { readonly [K in Key]?: ReadonlyArray<Choice> }\n }\n\n const [first] = lists\n if (first === undefined)\n throw new Error(\n 'Crud.options: give it the lists its pickers pick from; with none, it has nothing to read',\n )\n return Object.assign(choices, {\n /** For `Data.subscriptions`: what the pickers hold is read, so it can be named. */\n active: {\n name: `${pickers.map(([key]) => key).join('+')} chosen`,\n owner: first.owner,\n projectionOf: (root: Root) => {\n const rows = held(root)\n return rows.length === 0\n ? Option.none()\n : Option.some(\n Projection.struct(\n Object.fromEntries(rows.map(([key, list, id]) => [`${key}:${id}`, list.row(id)])),\n ),\n )\n },\n messages: [],\n } satisfies ActiveSurface<Root>,\n })\n },\n\n /**\n * What the pieces on a page require of Remote, by the names given: a placed\n * editor, list, or detail, or the pickers from `Crud.options`. It is each one's\n * `active`, gathered, so handing the pieces over is enough and none is left out:\n *\n * ```ts\n * Data.wiring(Crud.actives({ posts: Posts, authors: Authors, editor: PostEditor, pickers }))\n * ```\n */\n actives: <const Pieces extends Readonly<Record<string, { readonly active: unknown }>>>(\n pieces: Pieces,\n ): { readonly [K in keyof Pieces]: Pieces[K]['active'] } =>\n Object.fromEntries(Object.entries(pieces).map(([name, piece]) => [name, piece.active])) as {\n readonly [K in keyof Pieces]: Pieces[K]['active']\n },\n\n /**\n * An editor for one form and the mutation its value feeds. The form's value\n * must be the mutation's input, which is what declaring the input once and\n * giving it to both guarantees.\n */\n editor: <\n const Name extends string,\n FormName extends string,\n FormModel,\n FormMessage extends { readonly _tag: string },\n Value,\n E extends AnyEntity,\n Fields extends Schema.Struct.Fields,\n Members,\n R = never,\n >(\n name: Name,\n config: {\n readonly form: EditableForm<FormName, FormModel, FormMessage, Value, E, Fields, Members, R>\n readonly mutation: MutationDescriptor<string, Value, any>\n /**\n * Which of the form's keys the row being edited does not let its reader\n * set, from members loaded beside the form's: `foldkit-guard`'s\n * `May.locks(Todo)`. Read with `locked`.\n */\n readonly locks?: Locks | undefined\n },\n ) => {\n type Model = EditorModel<FormModel>\n const { form, mutation, locks } = config\n // How each input key's value is compared, built once from its own schema.\n const sameValue: Readonly<Record<string, (left: unknown, right: unknown) => boolean>> =\n Object.fromEntries(\n Object.entries(form.input.schema.fields).map(([key, schema]) => [\n key,\n Schema.toEquivalence(schema as Schema.Codec<unknown, unknown>),\n ]),\n )\n // The input key holding the revision a declared write expects, when it expects one.\n const expectKey = mutation.write?.expect?.key\n // The keys a refusal can name: a nested key holds rows, not a draft to mark.\n const draftKeys = new Set(\n form.controls.filter(entry => !Input.Nested.is(entry.control)).map(entry => entry.key),\n )\n // The form's key that sets each field, for an invariant's path, which names fields.\n const keyOfField = new Map<string, string>(\n Object.entries(form.input.members as Readonly<Record<string, InputMember>>).flatMap(\n ([key, member]) => {\n if (member._tag !== 'Field' || !draftKeys.has(key)) return []\n return [[member.key, key] as const]\n },\n ),\n )\n /** The keys a refusal names among the form's, each with what to say beside it. */\n const namedBy = (\n refusal: unknown,\n ): ReadonlyArray<{ readonly key: string; readonly error: string }> => {\n if (Refusal.isField(refusal)) {\n return draftKeys.has(refusal.key)\n ? [{ key: refusal.key, error: String(refusal.reason) }]\n : []\n }\n if (Refusal.isInvalid(refusal)) {\n return refusal.path.flatMap(field =>\n Option.toArray(Option.fromUndefinedOr(keyOfField.get(field))).map(key => ({\n key,\n error: refusal.message,\n })),\n )\n }\n return []\n }\n\n const closed: Model = {\n form: form.initial,\n mode: 'closed',\n target: null,\n filled: false,\n requestId: null,\n edited: false,\n sent: Option.none(),\n settledFor: Option.none(),\n refusedFor: Option.none(),\n filledWith: Option.none(),\n }\n const Model = Schema.Struct({\n form: form.bundle.Model,\n mode: Schema.Literals(['closed', 'new', 'edit']),\n target: Schema.NullOr(Schema.String),\n filled: Schema.Boolean,\n requestId: Schema.NullOr(Schema.String),\n edited: Schema.Boolean,\n sent: Schema.OptionFromNullOr(Schema.Record(Schema.String, Schema.Unknown)),\n settledFor: Schema.OptionFromNullOr(Schema.String),\n refusedFor: Schema.OptionFromNullOr(Schema.String),\n filledWith: Schema.OptionFromNullOr(Schema.Record(Schema.String, Schema.Unknown)),\n }) as unknown as Schema.Codec<Model, unknown>\n\n // The editor's Messages are the form's own: it adds state around the form,\n // not a second vocabulary.\n const bundle = Bundle.make(name, {\n Model,\n Message: form.bundle.Message,\n init: () => ({ model: closed }),\n update: (model: Model, message: FormMessage) => {\n const next = form.bundle.update(model.form, message, undefined)\n // An edit after a save starts a new round: the last save no longer\n // describes the form, though its answer is still the form's to take.\n // Any authored change counts, not only `Changed`: a row added or a\n // Bundle-backed control's edit is one too.\n const edited = model.edited || form.authoredChanged(model.form, next.model)\n const unchanged = next.model === model.form && edited === model.edited\n // The form's Commands are the editor's: a check the form started has to run.\n const step = {\n model: unchanged ? model : { ...model, form: next.model, edited },\n ...(next.commands === undefined ? {} : { commands: next.commands }),\n }\n return next.outMessage === undefined ? step : { ...step, outMessage: next.outMessage }\n },\n helpers: {\n /** Edits `id`: the form is emptied, and filled once the current values arrive. */\n open: (_: Model, id: IdOf<E>) => ({\n model: { ...closed, mode: 'edit' as const, target: id },\n }),\n /** A blank form for a new one: nothing to load. */\n blank: () => ({ model: { ...closed, mode: 'new' as const, filled: true } }),\n close: () => ({ model: closed }),\n },\n })\n\n const written = Entity.selectFor(form.input)\n // What is loaded: what the form writes, and what `locks` reads beside it,\n // which the form never fills from.\n const current: typeof written =\n locks === undefined\n ? written\n : (Entity.select(form.input.entity, {\n ...(written.members as Readonly<Record<string, unknown>>),\n ...locks.select,\n } as never) as typeof written)\n /** The member each form key writes: a field's own key, or the relation it points. */\n const writes: ReadonlyArray<readonly [keyof Fields & string, string]> = Object.entries(\n form.input.members as Readonly<Record<string, InputMember | undefined>>,\n ).flatMap(([key, member]) =>\n member === undefined\n ? []\n : Match.valueTags(member, {\n Field: ({ key: written }) => [[key as keyof Fields & string, written] as const],\n RelationInput: ({ relation }) => [\n [key as keyof Fields & string, relation.key] as const,\n ],\n NestedInput: () => [],\n Unmapped: () => [],\n }),\n )\n\n return {\n bundle,\n /** What the editor loads before it shows the form: every member the form writes. */\n selection: current,\n\n /**\n * The editor where it lives: `model` is its slice of the parent Model (the\n * field it was declared at), and `data` the Remote domain that registers the\n * mutation and the Entity.\n */\n at: <Root>(where: {\n readonly data: DomainLike<Root>\n readonly model: ModelRef<Root, Model>\n }) => {\n const { data, model: slice } = where\n const loaded = (root: Root): RemoteData<any> | undefined => {\n const { mode, target } = slice.get(root)\n return mode === 'edit' && target !== null\n ? data.get(current, target).read(root)\n : undefined\n }\n\n /**\n * Shows the loaded value once, the first time it is there. Later refreshes\n * leave the drafts alone. A value whose latest refresh failed is still the\n * last one known good, so it fills the form too: `status` goes on saying\n * `Editing`, and the failure is the read's to show.\n */\n /**\n * A save the server refused for the form's keys shows on those keys,\n * once per save: `Refusal.field`'s reason, as text, or `Refusal.invalid`'s\n * message beside each key its path names. Any other refusal, and one\n * naming no key of the form, is the status's to say.\n */\n const refused = (root: Root): Root => {\n const editor = slice.get(root)\n const { requestId } = editor\n if (requestId === null || Option.contains(editor.refusedFor, requestId)) return root\n const named = Option.match(refusalOf(root), { onNone: () => [], onSome: namedBy })\n if (named.length === 0) return root\n return slice.set(root, {\n ...editor,\n form: named.reduce(\n (shown, { key, error }) =>\n form.bundle.update(shown, form.Message.Refused({ key, error }), undefined).model,\n editor.form,\n ),\n refusedFor: Option.some(requestId),\n })\n }\n\n /** The value loaded, or the last one known good when a refresh failed. */\n const known = (root: Root): Option.Option<any> => {\n const read = loaded(root)\n return read === undefined\n ? Option.none()\n : RemoteData.match(read, {\n Initial: () => Option.none(),\n Loading: () => Option.none(),\n Ready: Option.some,\n Refreshing: Option.some,\n Failed: (_, previous) => previous,\n NotFound: () => Option.none(),\n })\n }\n\n const filledOf = (root: Root): Root => {\n const editor = slice.get(root)\n if (editor.filled) return root\n const shown = known(root)\n if (Option.isNone(shown)) return root\n const value = shown.value\n const values = Entity.valuesFor(form.input, value) as Partial<Value>\n return slice.set(root, {\n ...editor,\n form: form.fill(editor.form, values).model,\n filled: true,\n filledWith: Option.some(values),\n })\n }\n\n /**\n * The input keys a save changes from what the form was filled with, by\n * each key's own schema: none filled (something new) is every key, so\n * none are named.\n */\n const changedKeys = (editor: Model, value: Value): ReadonlyArray<string> | undefined => {\n // A submitted value is the form's decoded struct, so it reads by key.\n const submitted = value as Readonly<Record<string, unknown>>\n return Option.match(editor.filledWith, {\n onNone: () => undefined,\n onSome: filled =>\n Object.keys(submitted).filter(\n key => !Object.hasOwn(filled, key) || !sameValue[key]!(filled[key], submitted[key]),\n ),\n })\n }\n\n /** The revision the row is at now, as the form's `expect` key holds it. */\n const currentRevision = (root: Root): Option.Option<unknown> => {\n const read = loaded(root)\n if (expectKey === undefined || read === undefined) return Option.none()\n if (read._tag !== 'Ready' && read._tag !== 'Refreshing') return Option.none()\n const values = Entity.valuesFor(form.input, read.value) as Readonly<\n Record<string, unknown>\n >\n return Object.hasOwn(values, expectKey) ? Option.some(values[expectKey]) : Option.none()\n }\n /** Whether the row is at another revision than the one the form was filled with. */\n const moved = (root: Root): boolean => {\n const { filledWith } = slice.get(root)\n return (\n expectKey !== undefined &&\n Option.isSome(filledWith) &&\n Option.exists(\n currentRevision(root),\n revision => !sameValue[expectKey]!(filledWith.value[expectKey], revision),\n )\n )\n }\n\n /**\n * Once this editor's save lands, the form takes its answer, once: what\n * it sent is now what the row holds as the form knows it, and the\n * revision is the one it moved the row to, or its next save would\n * conflict with itself. A revision moved after that is another's, and\n * reads as `Moved`. A create's form starts blank again, its values now\n * the row's; one the author went on typing in is a new one's draft.\n */\n const settled = (root: Root): Root => {\n const editor = slice.get(root)\n const { requestId } = editor\n if (requestId === null || Option.contains(editor.settledFor, requestId)) return root\n if (saveOf(root)._tag !== 'Applied') return root\n const settledFor = Option.some(requestId)\n if (editor.mode === 'new') {\n return slice.set(root, {\n ...editor,\n settledFor,\n ...(editor.edited ? {} : { form: form.initial }),\n })\n }\n const revision = expectKey === undefined ? Option.none() : currentRevision(root)\n const filledWith = Option.some({\n ...Option.getOrElse(editor.filledWith, () => ({})),\n ...Option.getOrElse(editor.sent, () => ({})),\n ...Option.match(revision, {\n onNone: () => ({}),\n onSome: value => ({ [expectKey!]: value }),\n }),\n })\n return slice.set(root, {\n ...editor,\n settledFor,\n filledWith,\n form: Option.match(revision, {\n onNone: () => editor.form,\n onSome: value =>\n form.fill(editor.form, { [expectKey!]: value } as Partial<Value>).model,\n }),\n })\n }\n\n const sync: Update.Step<Root, never, never> = root => ({\n model: settled(refused(filledOf(root))),\n })\n\n /** Why the server refused the last save, when it refused it as data. */\n const refusalOf = (root: Root): Option.Option<unknown> => {\n const { requestId } = slice.get(root)\n return requestId === null ? Option.none() : data.refusal(root, requestId, mutation)\n }\n\n const saveOf = (root: Root): MutationStatus => {\n const { requestId } = slice.get(root)\n return requestId === null ? { _tag: 'Unknown' } : data.mutation(root, requestId)\n }\n\n return {\n /** For the placement: a valid submit becomes the mutation, and its request is remembered. */\n onOut:\n (submitted: Submitted<Value>): Step<Root> =>\n root => {\n // A declared write is told what changed, so it writes, and shows, only that.\n const started = data.mutate(\n root,\n mutation,\n submitted.value,\n mutation.write === undefined\n ? undefined\n : { keys: changedKeys(slice.get(root), submitted.value) },\n )\n const editor = slice.get(started.model)\n return {\n model: slice.set(started.model, {\n ...editor,\n requestId: started.requestId,\n edited: false,\n sent: Option.some(submitted.value as Readonly<Record<string, unknown>>),\n }),\n commands: [started.command],\n }\n },\n\n /**\n * For `Data.subscriptions`: while an id is being edited, what it loads is\n * a requirement like any Surface's, so Remote fetches and retains it.\n */\n active: data.active(name, root => {\n const { mode, target } = slice.get(root)\n return mode === 'edit' && target !== null\n ? Option.some(data.get(current, target))\n : Option.none()\n }),\n\n sync,\n\n /**\n * Wraps the application's `update` so `sync` runs after every Message:\n * the value may arrive with any of them.\n */\n after:\n <Message, Result extends { readonly model: Root }>(\n update: (model: Root, message: Message) => Result,\n ) =>\n (model: Root, message: Message): Result => {\n const next = update(model, message)\n return { ...next, model: sync(next.model).model }\n },\n\n /**\n * Asks for the value being edited again: `Data.refresh`, for a retry\n * button while `status` is `LoadFailed`. A failed read is not retried on\n * its own. Unchanged while nothing is open for editing.\n */\n refresh: (root: Root): Root => {\n const { mode, target } = slice.get(root)\n return mode === 'edit' && target !== null\n ? data.refresh(root, data.get(current, target))\n : root\n },\n\n /** Why the last save failed, while `status` is `SaveFailed`. */\n saveError: (root: Root): RemoteError | undefined => {\n const save = saveOf(root)\n return save._tag === 'Failed' ? save.error : undefined\n },\n\n /**\n * The form's keys the row being edited does not let its reader set, by\n * `locks`, for `foldkit-mixins-form`'s `locked`. None for something new,\n * whose create the server checks, and none until the row is loaded.\n */\n locked: (root: Root): ReadonlyArray<keyof Fields & string> =>\n locks === undefined\n ? []\n : Option.match(known(root), {\n onNone: () => [],\n onSome: row =>\n writes.flatMap(([key, member]) => (locks.locked(row, member) ? [key] : [])),\n }),\n\n /** The id being edited, typed as the Entity's own; `null` for a new one or a closed editor. */\n target: (root: Root): IdOf<E> | null => slice.get(root).target as IdOf<E> | null,\n status: (root: Root): EditorStatus => {\n const editor = slice.get(root)\n if (editor.mode === 'closed') return 'Closed'\n // Gone outranks everything: a save that landed describes a thing that no longer exists.\n const read = loaded(root)\n if (read?._tag === 'NotFound') return 'NotFound'\n const save = saveOf(root)\n // Changed since the last save: that save no longer describes the form.\n if (editor.edited) return moved(root) ? 'Moved' : 'Editing'\n switch (save._tag) {\n case 'Pending':\n return 'Saving'\n case 'Failed':\n return Option.exists(refusalOf(root), Refusal.isConflict)\n ? 'Conflict'\n : 'SaveFailed'\n case 'Applied':\n // Saved, and since moved on by someone else.\n return moved(root) ? 'Moved' : 'Saved'\n case 'Unknown':\n if (editor.filled) return moved(root) ? 'Moved' : 'Editing'\n return read?._tag === 'Failed' ? 'LoadFailed' : 'Loading'\n }\n },\n }\n },\n }\n },\n}\n\nexport { Display, type DisplayColumn, type DisplayKind, type DisplayWords } from './display.js'\nexport { Sort, type SortState, type SortedColumn } from './sort.js'\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["defineCommand"],"sources":["../src/display.ts","../src/screen.ts","../src/sort.ts","../src/index.ts"],"sourcesContent":["/**\n * Displays: how one selected member shows in a list or a detail, described\n * without a renderer. It is `foldkit-form`'s `Input` from the reading side: the\n * member says what it is, a Display says how it reads, and which DOM draws it is\n * the application's.\n */\nimport { Schema, SchemaAST } from 'effect'\nimport { SchemaShape, type AnyEntity, type EntityMember } from 'foldkit-entity'\nimport { Metadata } from 'foldkit-metadata'\n\n/**\n * How one selected member shows: the primitive every kind of Display is a value\n * of. `Display.text()` and `Display.flag()` are values of it, made by\n * `Display.kind`, which is what an application calls for a badge or a relative\n * date. The kinds here are a collection, not a special case.\n */\nexport interface Display<Data = unknown> {\n /** The name a renderer is found by. */\n readonly kind: string\n /** Whether a view draws it. A member read and not shown, usually the id, is `false`. */\n readonly shown: boolean\n /** What this kind needs: a format, a relation's target. */\n readonly data: Data\n /**\n * The value as text: what a cell says when nothing draws it specially. It is\n * never given `null` or `undefined`; `Display.show` says `nothing` for those.\n */\n readonly text: (value: unknown, words: DisplayWords) => string\n}\n\n/** A kind of Display: how its values are made, and how one is told from another. */\nexport interface DisplayKind<Data> {\n readonly kind: string\n readonly of: (data: Data) => Display<Data>\n readonly is: (display: Display) => display is Display<Data>\n}\n\n/** A selected member with its words and its Display. A list's column, a detail's line. */\nexport interface DisplayColumn<Key extends string = string> {\n readonly key: Key\n readonly label: string\n readonly member: EntityMember\n readonly display: Display\n}\n\n/** The words a Display needs that are not the value's own. */\nexport interface DisplayWords {\n /** Default `yes`. */\n readonly yes?: string\n /** Default `no`. */\n readonly no?: string\n /** What stands for nothing: `null`, `undefined`, an empty list. Default the empty string. */\n readonly nothing?: string\n /** Between the items of a list, and between the members of a nested value. Default `, `. */\n readonly separator?: string\n}\n\nconst key = Metadata.key<Display>('foldkit-crud/display', {\n // The last Display attached wins: a later annotation refines an earlier one.\n merge: displays => displays.slice(-1),\n summarize: display => display.kind,\n})\n\nconst fromSchema = (schema: Schema.Top): Display => {\n const members = SchemaShape.present(Schema.toType(schema).ast)\n const [only] = members\n if (members.length === 1 && only !== undefined) {\n if (SchemaAST.isNumber(only)) return Number_.of({})\n if (SchemaAST.isBoolean(only)) return Flag.of(nothing)\n }\n return Text.of({})\n}\n\n/** A kind of Display. The kinds below are made with it, and so is an application's. */\nconst kind = <Data = Record<string, never>>(\n name: string,\n spec: {\n readonly shown?: boolean\n readonly text: (data: Data, value: unknown, words: DisplayWords) => string\n },\n): DisplayKind<Data> => ({\n kind: name,\n of: data =>\n Object.freeze({\n kind: name,\n shown: spec.shown ?? true,\n data,\n text: (value: unknown, words: DisplayWords) => spec.text(data, value, words),\n }),\n is: (display): display is Display<Data> => display.kind === name,\n})\n\nconst show = (display: Display, value: unknown, words: DisplayWords = {}): string =>\n value === null || value === undefined ? (words.nothing ?? '') : display.text(value, words)\n\nconst Text = kind<{ readonly format?: ((value: unknown) => string) | undefined }>('Text', {\n text: (data, value) => (data.format === undefined ? String(value) : data.format(value)),\n})\nconst Number_ = kind<{ readonly format?: ((value: number) => string) | undefined }>('Number', {\n text: (data, value) =>\n typeof value === 'number' && data.format !== undefined ? data.format(value) : String(value),\n})\nconst Flag = kind('Flag', {\n text: (_, value, words) => (value === true ? (words.yes ?? 'yes') : (words.no ?? 'no')),\n})\nconst Hidden = kind('Hidden', { shown: false, text: () => '' })\n/** A relation read as refs: which one, with nothing to say about it but its id. */\nconst Ref = kind<{ readonly target: AnyEntity; readonly many: boolean }>('Ref', {\n text: (_, value, words) => {\n const ids = (Array.isArray(value) ? value : [value]).map(ref =>\n typeof ref === 'object' && ref !== null && 'id' in ref ? String(ref.id) : String(ref),\n )\n return ids.length === 0 ? (words.nothing ?? '') : ids.join(words.separator ?? ', ')\n },\n})\n/**\n * A relation read through a Selection: the target's own members, each with a\n * Display. `shape` is how many there are: one, a list, or a page of a list.\n */\nconst Nested = kind<{\n readonly target: AnyEntity\n readonly shape: 'one' | 'many' | 'page'\n readonly columns: ReadonlyArray<DisplayColumn>\n}>('Nested', {\n text: (data, value, words) => {\n const one = (item: unknown): string =>\n data.columns\n .filter(column => column.display.shown)\n .map(column =>\n show(\n column.display,\n (item as Readonly<Record<string, unknown>> | null)?.[column.key],\n words,\n ),\n )\n .filter(text => text !== '')\n .join(' ')\n const items =\n data.shape === 'one'\n ? [value]\n : data.shape === 'page'\n ? ((value as { readonly items?: ReadonlyArray<unknown> }).items ?? [])\n : (value as ReadonlyArray<unknown>)\n return items.length === 0 ? (words.nothing ?? '') : items.map(one).join(words.separator ?? ', ')\n },\n})\n\nconst nothing: Record<string, never> = Object.freeze({})\n\nexport const Display = {\n /**\n * A kind of Display. `Display.kind<{ tone: string }>('Badge', { text: (_, value) => String(value) })`\n * gives `.of(data)` to make one and `.is(display)` to tell one, exactly as the\n * kinds below were made. `text` is the floor; a view draws it specially once\n * it is given a renderer for `Badge`.\n */\n kind,\n\n // The kinds this package resolves to. A view finds its renderer by `kind`.\n Text,\n Number: Number_,\n Flag,\n Hidden,\n Ref,\n Nested,\n\n text: (format?: (value: unknown) => string): Display => Text.of({ format }),\n number: (format?: (value: number) => string): Display => Number_.of({ format }),\n flag: (): Display => Flag.of(nothing),\n /** Read, so a row can be opened or keyed by it, and not shown: usually the id. */\n hidden: (): Display => Hidden.of(nothing),\n\n /**\n * Entity metadata: how a member shows wherever it is listed, e.g.\n * `Entity.annotateMembers({ price: Display.of(Display.number(cents => ...)) })`.\n */\n of: (display: Display) => key.of(display),\n\n /**\n * The Display of one selected member, from the most to the least explicit\n * source: the member's `Display.of` metadata, how the relation was selected,\n * then the shape of its schema. `nested` is the columns of a nested Selection,\n * with `shape` saying how many the relation yields.\n */\n resolve: (\n member: EntityMember,\n nested?: {\n readonly shape: 'one' | 'many' | 'page'\n readonly columns: ReadonlyArray<DisplayColumn>\n },\n ): Display => {\n const [explicit] = key.get(member.metadata)\n if (explicit !== undefined) return explicit\n if (member._tag !== 'Relation') return fromSchema(member.schema as Schema.Top)\n const target = member.target()\n return nested === undefined\n ? Ref.of({ target, many: member.cardinality === 'many' })\n : Nested.of({ target, shape: nested.shape, columns: nested.columns })\n },\n\n /**\n * A value as the text its Display calls for: what a cell says when nothing\n * draws it specially. A view that wants a link or a badge reads the Display\n * and the value itself; this is the floor every view can stand on.\n */\n show,\n}\n","/**\n * A CRUD screen's Messages and their handling, for a page that places a\n * `Crud.editor` and a `Crud.remover` itself: opening a row, starting a new one,\n * closing, and asking to delete, each answered through the placements' own\n * helpers, and the editor's sync run after every Message. It places nothing and\n * owns no Model: the children, their `onOut`, the query's input and the wiring\n * stay the page's, so the pieces are as usable without it as with it.\n */\nimport { Option, Schema } from 'effect'\n\n/** The four Messages a screen answers, named after it: `OpenedPost`, `CreatedPost`, … */\nexport type ScreenCases<Name extends string, Id extends Schema.Top> = {\n readonly [K in `Opened${Name}`]: { readonly id: Id }\n} & { readonly [K in `Created${Name}`]: {} } & { readonly [K in `Closed${Name}`]: {} } & {\n readonly [K in `AskedToRemove${Name}`]: { readonly id: Id }\n}\n\n/**\n * What the screen asks of the page's placed pieces. `Out` and `Asked` are what\n * the editor's and the remover's helpers return, the page's own Model and\n * Commands, which the screen hands back.\n */\nexport interface ScreenPieces<\n Root,\n Id,\n Out extends { readonly model: Root },\n Asked extends { readonly model: Root },\n> {\n /** The placed editor (`Editor.at(...)`), whose sync runs after every Message. */\n readonly editor: {\n readonly after: <M, R extends { readonly model: Root }>(\n update: (model: Root, message: M) => R,\n ) => (model: Root, message: M) => R\n }\n /**\n * The editor's placement (`Page.children.editor`): its `open`, `blank` and\n * `close`, and its link, which reads the form's Messages out of the page's.\n */\n readonly editing: {\n readonly helpers: {\n readonly open: (id: Id) => (model: Root) => Out\n readonly blank: () => (model: Root) => Out\n readonly close: () => (model: Root) => Out\n }\n readonly link: { readonly fromParentMessage: (message: never) => Option.Option<unknown> }\n }\n /** The remover's placement, when the screen can delete: its `ask`. */\n readonly removing?:\n | { readonly helpers: { readonly ask: (id: Id) => (model: Root) => Asked } }\n | undefined\n /**\n * The form's pickers (`Crud.options`), when they page: a picker's\n * `AskedForMore` shows one more page of its list, and still reaches the form.\n */\n // `NoInfer`: the page's Model comes from its own `update`, so pickers over\n // another Model are the mistake reported.\n readonly pickers?:\n // The key is one of the form's, read out of the Message untyped.\n { readonly ask: (root: NoInfer<Root>, key: never) => NoInfer<Root> } | undefined\n}\n\nconst isAskedForMore = Schema.is(Schema.TaggedStruct('AskedForMore', { key: Schema.String }))\n\nexport const screen = <const Name extends string, Id extends Schema.Top>(\n name: Name,\n config: { readonly id: Id },\n) => {\n const opened = Schema.TaggedStruct(`Opened${name}`, { id: config.id })\n const created = Schema.TaggedStruct(`Created${name}`, {})\n const closed = Schema.TaggedStruct(`Closed${name}`, {})\n const asked = Schema.TaggedStruct(`AskedToRemove${name}`, { id: config.id })\n const isOpened = Schema.is(opened)\n const isCreated = Schema.is(created)\n const isClosed = Schema.is(closed)\n const isAsked = Schema.is(asked)\n // The id of a Message one of the guards above has just checked: the Schema\n // decoded it, but with `Id` still generic the checker cannot see its field.\n const idOf = (message: object): Id['Type'] => (message as { readonly id: Id['Type'] }).id\n const messages = {\n [`Opened${name}`]: { id: config.id },\n [`Created${name}`]: {},\n [`Closed${name}`]: {},\n [`AskedToRemove${name}`]: { id: config.id },\n } as ScreenCases<Name, Id>\n\n return {\n /** For `Bundle.withMessages`, beside the page's own. */\n messages,\n\n /**\n * Wraps the page's `update`: the screen's Messages are answered here, and\n * every other goes to `update`; the editor's sync runs after each, so the\n * loaded value is shown whichever Message brings it.\n */\n update: <\n Root,\n Message extends { readonly _tag: string },\n Result extends { readonly model: Root },\n Out extends { readonly model: Root },\n Asked extends { readonly model: Root } = never,\n >(\n pieces: ScreenPieces<Root, Id['Type'], Out, Asked>,\n update: (model: Root, message: Message) => Result,\n ) =>\n // Wrapped once, when the page's update is made, not on every Message.\n pieces.editor.after((root: Root, received: Message): Result | Out | Asked => {\n if (isOpened(received)) return pieces.editing.helpers.open(idOf(received))(root)\n if (isCreated(received)) return pieces.editing.helpers.blank()(root)\n if (isClosed(received)) return pieces.editing.helpers.close()(root)\n if (isAsked(received) && pieces.removing !== undefined)\n return pieces.removing.helpers.ask(idOf(received))(root)\n const { pickers } = pieces\n if (pickers === undefined) return update(root, received)\n const asked = Option.filter(\n pieces.editing.link.fromParentMessage(received as never),\n isAskedForMore,\n )\n return update(\n Option.match(asked, {\n onNone: () => root,\n onSome: ({ key }) => pickers.ask(root, key as never),\n }),\n received,\n )\n }),\n }\n}\n","/**\n * Sorting a list, written once. How a list is sorted is the application's state\n * and its query's input, so this holds none of it: it is the schema of that\n * state, what a click on a header makes of it, and the shape a drawn table\n * takes. The server reads the same state and decides which columns it means.\n */\nimport { Schema } from 'effect'\n\n/** How a list is sorted: by which of its orders, which way; `null` for the server's own order. */\nexport type SortState<Column extends string> = {\n readonly by: Column\n readonly direction: 'asc' | 'desc'\n} | null\n\n/** How one column is sorted now, and the Message a click on its header sends. */\nexport interface SortedColumn<Message> {\n readonly direction?: 'asc' | 'desc' | undefined\n readonly message: Message\n}\n\nexport const Sort = {\n /**\n * The orders a list offers, by name. A name is an order the server knows, not\n * a column: which columns may sort, and what `title` means in SQL, stay the\n * server's to decide.\n */\n make: <const Columns extends readonly [string, ...string[]]>(columns: Columns) => {\n type Column = Columns[number]\n type State = SortState<Column>\n /** A click on a header: ascending, then descending, then the server's own order again. */\n const toggle = (current: State, column: Column): State =>\n current === null || current.by !== column\n ? { by: column, direction: 'asc' }\n : current.direction === 'asc'\n ? { by: column, direction: 'desc' }\n : null\n return {\n columns,\n /** For the Model field, and for the query's input. */\n Schema: Schema.NullOr(\n Schema.Struct({\n by: Schema.Literals(columns),\n direction: Schema.Literals(['asc', 'desc']),\n }),\n ) as unknown as Schema.Codec<State, State>,\n /** The server's own order. */\n none: null as State,\n toggle,\n /**\n * What a drawn table takes as `sort`: every order with how it is sorted now\n * and the Message a click sends, made from the state the click leads to and\n * the column clicked. A click meets the state last drawn, so two clicks\n * inside one frame both send the same `next`; send the column instead\n * (`(_, column) => Sorted({ column })`) and `toggle` it in `update`, from\n * the Model as it is then.\n */\n inputs: <Message>(\n current: State,\n message: (next: State, column: Column) => Message,\n ): { readonly [K in Column]: SortedColumn<Message> } =>\n Object.fromEntries(\n columns.map(column => [\n column,\n {\n direction: current?.by === column ? current.direction : undefined,\n message: message(toggle(current, column), column),\n },\n ]),\n ) as { readonly [K in Column]: SortedColumn<Message> },\n }\n },\n}\n","/**\n * `foldkit-crud` — management screens assembled from parts an application\n * already has.\n *\n * An editor joins a `foldkit-form` form, the Remote mutation its value feeds,\n * and the Entity they share: it loads what the form writes, shows it, turns a\n * valid submit into the mutation, and says how that went. Everything it makes is\n * ordinary Foldkit: a Bundle, an ActiveSurface, Update Steps. Nothing is\n * generated from an Entity alone; each capability is declared.\n */\nimport { Effect, Match, Option, Schema } from 'effect'\nimport { Bundle } from 'foldkit-bundle'\nimport {\n Entity,\n SelectionPageTypeId,\n Words,\n type AnyEntity,\n type EntityInput,\n type EntityMember,\n type InputMember,\n type Selection,\n type IdOf,\n type SelectionPage,\n} from 'foldkit-entity'\nimport { Form, Input, type FormControl, type Submitted } from 'foldkit-form'\nimport { Display, type DisplayColumn } from './display.js'\nimport { screen } from './screen.js'\nimport {\n Refusal,\n RemoteData,\n type MutationDescriptor,\n type Page,\n type QueryDescriptor,\n type MutationStatus,\n type RemoteClient,\n type RemoteError,\n type RemoteMessage,\n} from 'foldkit-remote'\nimport { Projection, type ActiveSurface, type ModelRef } from 'foldkit-surface'\nimport { define as defineCommand, type Command } from 'foldkit/command'\nimport * as Dom from 'foldkit/dom'\nimport { defineMessageUnion } from 'foldkit/message'\nimport type { Html, HtmlBuilder } from 'foldkit/html'\nimport * as Submodel from 'foldkit/submodel'\nimport type * as Update from 'foldkit/update'\n\n/** The parts of a `Form.make` result an editor wraps. */\nexport interface EditableForm<\n Name extends string,\n FormModel,\n FormMessage,\n Value,\n E extends AnyEntity,\n Fields extends Schema.Struct.Fields,\n Members,\n R = never,\n> {\n readonly bundle: {\n readonly name: Name\n readonly Model: Schema.Codec<FormModel, unknown>\n readonly Message: Schema.Codec<FormMessage, unknown>\n readonly update: (\n model: FormModel,\n message: FormMessage,\n args: void,\n ) => Update.ReturnWithOutMessage<FormModel, FormMessage, Submitted<Value>, R>\n }\n readonly input: EntityInput<E, Fields, Members>\n readonly initial: FormModel\n readonly fill: (model: FormModel, values: Partial<Value>) => { readonly model: FormModel }\n readonly authoredChanged: (before: FormModel, after: FormModel) => boolean\n readonly controls: ReadonlyArray<FormControl>\n readonly Message: {\n // Method syntax: the form's own takes only its keys, which the editor checks first.\n Refused(fields: { readonly key: string; readonly error: string }): FormMessage\n }\n}\n\n/**\n * An editor's slice of the parent Model. `target` is the id being edited, or\n * `null` for a new one; `filled` is whether the loaded value has been shown;\n * `requestId` is the save in progress or last settled.\n */\n/**\n * Which members of a row its reader may not set, read from members loaded\n * beside the form's: what `Crud.editor`'s `locks` takes. `foldkit-guard`'s\n * `May.locks(Todo)` is one, from the server's `may`.\n */\nexport interface Locks {\n /** The members to load beside the form's, by key: `{ may: true }`. */\n readonly select: Readonly<Record<string, true>>\n /** Whether `member` of the loaded row may not be set by its reader. */\n readonly locked: (row: Readonly<Record<string, unknown>>, member: string) => boolean\n}\n\nexport interface EditorModel<FormModel> {\n readonly form: FormModel\n readonly mode: 'closed' | 'new' | 'edit'\n readonly target: string | null\n readonly filled: boolean\n readonly requestId: string | null\n /** Whether the author changed the form since its last save was sent. */\n readonly edited: boolean\n /** What the last save sent, for the form to take once it lands. */\n readonly sent: Option.Option<Readonly<Record<string, unknown>>>\n /** The save whose answer the form already took, so it is taken once. */\n readonly settledFor: Option.Option<string>\n /** The save whose refusal of a field the form already shows, so it is shown once. */\n readonly refusedFor: Option.Option<string>\n /**\n * The input values the row held as the form knows it: filled from it, and\n * moved on by each save of this editor's. A save compares to it, to send\n * only what changed.\n */\n readonly filledWith: Option.Option<Readonly<Record<string, unknown>>>\n}\n\n/**\n * A remover's slice of the parent Model: the id it was asked to delete and is\n * waiting on a yes for, and the delete in progress or last settled.\n */\nexport interface RemoverModel {\n readonly target: string | null\n readonly requestId: string | null\n}\n\n/** A remover's out Message: the user said yes to deleting `id`. */\nexport interface ConfirmedRemoval {\n readonly _tag: 'Confirmed'\n readonly id: string\n}\n\nexport type RemoverStatus =\n | 'Idle'\n /** Asked to delete an id, and waiting for a yes or a no. */\n | 'Confirming'\n | 'Deleting'\n | 'Deleted'\n | 'DeleteFailed'\n\nexport type EditorStatus =\n | 'Closed'\n /** Editing an id whose current values have not arrived. */\n | 'Loading'\n | 'NotFound'\n | 'LoadFailed'\n | 'Editing'\n | 'Saving'\n | 'Saved'\n | 'SaveFailed'\n /** The server refused the save with `Refusal.conflict`: the row moved on since it was read. */\n | 'Conflict'\n /**\n * Someone else saved the row since it was read: its revision moved past the\n * one the form holds. The draft is kept; a save of it would be a conflict.\n */\n | 'Moved'\n\n/** The parts of a bound Remote domain an editor uses. */\nexport interface DomainLike<Root> {\n // Method syntax: the descriptors are checked where the editor is made, not here.\n mutate(\n model: Root,\n mutation: any,\n input: any,\n options?: { readonly keys?: ReadonlyArray<string> | undefined },\n ): {\n readonly model: Root\n readonly requestId: string\n readonly command: Command<RemoteMessage, never, RemoteClient>\n }\n get(selection: any, id: string): Projection<Root, RemoteData<any>>\n query(query: any, input: any, options: any): Projection<Root, RemoteData<Page<any>>>\n more(model: Root, projection: any): Option.Option<Root>\n refresh(model: Root, target: any): Root\n mutation(model: Root, requestId: string): MutationStatus\n refusal(model: Root, requestId: string, mutation: any): Option.Option<unknown>\n /** `Data.active`: a read of the domain as an active Surface of its application. */\n active(\n name: string,\n projectionOf: (model: Root) => Option.Option<Projection<Root, any>>,\n ): ActiveSurface<Root>\n}\n\ntype Step<Root> = Update.Step<Root, RemoteMessage, RemoteClient>\n\n/** One thing a relation picker offers. */\nexport interface Choice {\n readonly value: string\n readonly label: string\n}\n\n/**\n * One column of a list, or one line of a detail: a member the Selection reads,\n * with its words and its `Display`.\n */\nexport type ListColumn<Key extends string = string> = DisplayColumn<Key>\n\nconst columnLabel = (key: string, member: EntityMember): string =>\n Option.getOrElse(\n member._tag === 'Relation' ? Option.none() : Words.of(member.schema as Schema.Top).title,\n () => Form.labelOf(member) ?? key,\n )\n\ntype AnySelection = Selection<string, unknown, Schema.Constraint>\n\n/** The selected members in the Selection's order, each with its label and its Display. */\nconst columnsOf = <Members>(\n selection: Selection<string, Members, Schema.Constraint>,\n): ReadonlyArray<ListColumn<keyof Members & string>> => {\n const members: Readonly<Record<string, EntityMember>> = selection.entity.members\n const selected = selection.members as Readonly<\n Record<string, true | AnySelection | SelectionPage<string, Schema.Constraint>>\n >\n return Object.keys(selected).map(key => {\n const member = members[key]!\n const how = selected[key]!\n // A relation read through a Selection shows the target's own members.\n const nested =\n how === true || member._tag !== 'Relation'\n ? undefined\n : SelectionPageTypeId in how\n ? { shape: 'page' as const, columns: columnsOf(how.selection) }\n : {\n shape: member.cardinality === 'many' ? ('many' as const) : ('one' as const),\n columns: columnsOf(how),\n }\n return {\n key: key as keyof Members & string,\n label: columnLabel(key, member),\n member,\n display: Display.resolve(member, nested),\n }\n })\n}\n\nconst RemoverMessage = defineMessageUnion({\n Confirmed: {},\n Cancelled: {},\n /** Focus went back to what asked, after a no took the question away. */\n CompletedFocus: {},\n})\ntype RemoverMessage = typeof RemoverMessage.Type\n\nconst idle: RemoverModel = { target: null, requestId: null }\n\nconst FocusAsker = defineCommand('Crud.remover.Focus', {\n args: { selector: Schema.String },\n messages: [RemoverMessage.CompletedFocus],\n // What asked may be gone by now, and nothing to focus is nothing to do.\n execute: ({ selector }) =>\n Dom.focus(selector).pipe(Effect.ignore, Effect.as(RemoverMessage.CompletedFocus())),\n})\n\nexport const Crud = {\n /**\n * One Entity read through a Selection, for a page that shows it. Like a list\n * it holds no state: the value is Remote's and the id is the application's.\n */\n detail: <const Name extends string, EntityName extends string, Members, Row>(\n name: Name,\n config: {\n readonly selection: Selection<EntityName, Members, Schema.Constraint & { readonly Type: Row }>\n },\n ) => ({\n name,\n /** The selected members in the Selection's order, each with its label. */\n fields: columnsOf(config.selection),\n /** Type-only: the value shown, for a view to be typed by. Never read. */\n Value: undefined as unknown as Row,\n\n /** The detail where it lives: `id` is the one shown, none while none is. */\n at: <Root>(where: {\n readonly data: DomainLike<Root>\n readonly id: (root: Root) => Option.Option<string>\n }) => {\n const projectionOf = (root: Root) =>\n Option.map(where.id(root), id => where.data.get(config.selection, id))\n return {\n /** For `Data.subscriptions`: the value is fetched and retained while an id is shown. */\n active: where.data.active(name, projectionOf),\n value: (root: Root): RemoteData<Row> =>\n Option.match(projectionOf(root), {\n onNone: () => ({ _tag: 'Initial' }),\n onSome: projection => projection.read(root),\n }),\n /**\n * Asks for the value again: `Data.refresh` over this detail, for a retry\n * button. A failed read is not retried on its own. Unchanged while no id\n * is shown.\n */\n refresh: (root: Root): Root =>\n Option.match(projectionOf(root), {\n onNone: () => root,\n onSome: projection => where.data.refresh(root, projection),\n }),\n }\n },\n }),\n\n /**\n * A screen's Messages (`OpenedPost`, `CreatedPost`, `ClosedPost`,\n * `AskedToRemovePost`) and an `update` that answers them through the editor's\n * and the remover's placements, for a page that places both itself.\n */\n screen,\n\n /**\n * Deleting through one mutation, with a yes in between: asked, confirmed,\n * deleted. What the mutation's input is for an id is `input`'s to say, and the\n * id it names there (`(id: PostId) => ...`) is the id `ask` takes.\n */\n remover: <\n const Name extends string,\n Input,\n Id extends string = string,\n const Key extends keyof Input = never,\n >(\n name: Name,\n config:\n | {\n readonly mutation: MutationDescriptor<string, Input, any>\n readonly input: (id: Id) => Input\n }\n | {\n readonly mutation: MutationDescriptor<string, Input, any>\n /**\n * The key of the mutation's input that holds the id, when that is all the\n * input is: `id: 'id'` for `{ id }`. The id's type is that key's.\n */\n readonly id: Key & (Input extends Readonly<Record<Key, string>> ? Key : never)\n },\n options: {\n /**\n * Where focus goes after a no takes the question away: the selector of the\n * button that asked (`'#delete'`), or one for the id asked about, for a\n * list with a button per row (`id => '#delete-' + id`). Without it focus\n * falls to the page.\n */\n readonly returnFocus?: string | ((id: string) => string) | undefined\n } = {},\n ) => {\n type AskedId = [Key] extends [never] ? Id : Input[Key] & string\n const inputFor = (id: string): Input =>\n 'input' in config ? config.input(id as Id) : ({ [config.id]: id } as Input)\n const bundle = Bundle.make(name, {\n Model: Schema.Struct({\n target: Schema.NullOr(Schema.String),\n requestId: Schema.NullOr(Schema.String),\n }) as unknown as Schema.Codec<RemoverModel, unknown>,\n Message: RemoverMessage,\n init: () => ({ model: idle }),\n update: (model: RemoverModel, message: RemoverMessage) =>\n RemoverMessage.match<\n Update.ReturnWithOutMessage<RemoverModel, RemoverMessage, ConfirmedRemoval, never>\n >(message, {\n Cancelled: () => {\n const { returnFocus } = options\n // A no to no question took nothing away: focus stays where it is.\n if (model.target === null || returnFocus === undefined) return { model: idle }\n const selector =\n typeof returnFocus === 'string' ? returnFocus : returnFocus(model.target)\n return { model: idle, commands: [FocusAsker({ selector })] }\n },\n Confirmed: () =>\n model.target === null\n ? { model: idle }\n : { model, outMessage: { _tag: 'Confirmed', id: model.target } as ConfirmedRemoval },\n CompletedFocus: () => ({ model }),\n }),\n helpers: {\n /** Asks whether to delete `id`. Nothing is deleted until `Confirmed`. */\n ask: (_: RemoverModel, id: AskedId) => ({ model: { target: id, requestId: null } }),\n dismiss: () => ({ model: idle }),\n },\n })\n\n return {\n bundle,\n /** The remover's Messages, for the yes and the no of a confirmation. */\n Message: RemoverMessage,\n\n at: <Root>(where: {\n readonly data: DomainLike<Root>\n readonly model: ModelRef<Root, RemoverModel>\n }) => {\n const { data, model: slice } = where\n const outcome = (root: Root): MutationStatus => {\n const { requestId } = slice.get(root)\n return requestId === null ? { _tag: 'Unknown' } : data.mutation(root, requestId)\n }\n return {\n /** For the placement: a yes becomes the mutation, and its request is remembered. */\n onOut:\n (confirmed: ConfirmedRemoval): Step<Root> =>\n root => {\n const started = data.mutate(root, config.mutation, inputFor(confirmed.id))\n return {\n model: slice.set(started.model, {\n target: confirmed.id,\n requestId: started.requestId,\n }),\n commands: [started.command],\n }\n },\n\n /** The id being asked about or deleted, for the words of a confirmation. */\n target: (root: Root): AskedId | null => slice.get(root).target as AskedId | null,\n\n status: (root: Root): RemoverStatus => {\n const { target } = slice.get(root)\n const deleting = outcome(root)\n return deleting._tag === 'Pending'\n ? 'Deleting'\n : deleting._tag === 'Applied'\n ? 'Deleted'\n : deleting._tag === 'Failed'\n ? 'DeleteFailed'\n : target === null\n ? 'Idle'\n : 'Confirming'\n },\n\n /** Why the last delete failed, while `status` is `DeleteFailed`. */\n error: (root: Root): RemoteError | undefined => {\n const deleting = outcome(root)\n return deleting._tag === 'Failed' ? deleting.error : undefined\n },\n }\n },\n }\n },\n\n /**\n * A list over one query: which rows to show is the query's, what to show of\n * each is the Selection's. The pages live in Remote; the list holds no state.\n */\n list: <\n const Name extends string,\n EntityName extends string,\n Members,\n S extends Schema.Constraint,\n Input,\n >(\n name: Name,\n config: {\n readonly query: QueryDescriptor<string, Input, any>\n // The row is the Selection's own value, so it is typed with or without a `choice`.\n readonly selection: Selection<EntityName, Members, S>\n /** How many rows a page holds. Default 25. */\n readonly pageSize?: number\n /**\n * How a row reads as a choice in a relation picker: its id, and the words\n * that identify it to a person. Give it to a list that feeds pickers.\n */\n readonly choice?: {\n readonly value: (row: S['Type']) => string\n readonly label: (row: S['Type']) => string\n }\n },\n ) => {\n const { query, selection, pageSize = 25, choice } = config\n const columns = columnsOf(selection)\n\n return {\n name,\n /** The selected members in the Selection's order, each with its label. */\n columns,\n /** Type-only: a row, for a view to be typed by. Never read. */\n Row: undefined as unknown as S['Type'],\n\n /**\n * The list where it lives. `input` is the query's input as the Model has\n * it (filters, a search term), or `undefined` while the list is not shown.\n */\n at: <Root>(where: {\n readonly data: DomainLike<Root>\n /** The list's input from the Model; none while the list is not shown. */\n readonly input: (root: Root) => Option.Option<Input>\n }) => {\n const { data, input } = where\n const projectionOf = (root: Root) =>\n Option.map(input(root), value =>\n data.query(query, value, { select: selection, first: pageSize }),\n )\n const page = (root: Root): RemoteData<Page<S['Type']>> =>\n Option.match(projectionOf(root), {\n onNone: () => ({ _tag: 'Initial' }),\n onSome: projection => projection.read(root),\n })\n\n const active = data.active(name, projectionOf)\n\n return {\n /** For `Data.subscriptions`: the page and its rows are fetched and retained while shown. */\n active,\n\n page,\n\n /**\n * \"Load more\": the Model with the list showing one page more, for the\n * read entry to fetch; none when it shows every row there is.\n */\n more: (root: Root): Option.Option<Root> =>\n Option.flatMap(projectionOf(root), projection => data.more(root, projection)),\n\n /** Whether the page read says more rows follow it: what a picker's \"more\" offers. */\n hasMore: (root: Root): boolean => {\n const read = page(root)\n return (read._tag === 'Ready' || read._tag === 'Refreshing') && read.value.hasNext\n },\n\n /**\n * Asks for the list again: `Data.refresh` over its page and rows, for a\n * retry button. A failed read is not retried on its own. Unchanged while\n * the list is not shown.\n */\n refresh: (root: Root): Root =>\n Option.match(projectionOf(root), {\n onNone: () => root,\n onSome: projection => data.refresh(root, projection),\n }),\n\n name,\n /** Whose requirement the list is, for anything that requires beside it. */\n owner: active.owner,\n /**\n * One of the list's Entity through the list's Selection, by id, whether or\n * not the query finds it now: what a picker reads to name what is chosen.\n */\n row: (id: string): Projection<Root, RemoteData<any>> => data.get(selection, id),\n /** The Entity the rows are of, which is how a picker finds the list for its target. */\n entity: selection.entity as AnyEntity,\n /** Whether the list was given a `choice`, so its rows can be a picker's choices. */\n offersChoices: choice !== undefined,\n\n /**\n * The loaded rows as a picker's choices, read through `choice`. Listing a\n * relation's target is this query, which the application chose and the\n * server authorizes; nothing is read because a relation exists.\n */\n choices: (root: Root): ReadonlyArray<Choice> => {\n if (choice === undefined)\n throw new Error(`Crud list \"${name}\": give it a \"choice\" to use its rows in a picker`)\n const read = page(root)\n return read._tag === 'Ready' || read._tag === 'Refreshing'\n ? read.value.items.map(row => ({\n value: choice.value(row),\n label: choice.label(row),\n }))\n : []\n },\n\n /** That row as a choice, once it is read. */\n choiceOf: (root: Root, id: string): Choice | undefined => {\n if (choice === undefined) return undefined\n const read = data.get(selection, id).read(root)\n return read._tag === 'Ready' || read._tag === 'Refreshing'\n ? { value: choice.value(read.value), label: choice.label(read.value) }\n : undefined\n },\n }\n },\n }\n },\n\n /**\n * A form's Submodel view as the view of the editor that wraps it, for\n * `Bundle.withView`: the editor's Messages are the form's, and its Model holds\n * the form's under `form`.\n *\n * ```ts\n * Editor.bundle.pipe(Bundle.withView(Crud.editorView(FormView.submodel(form, view))))\n * ```\n */\n editorView: <FormModel, Message, ViewInputs>(\n view: Submodel.View<FormModel, Message, ViewInputs>,\n ): Submodel.View<EditorModel<FormModel>, Message, ViewInputs> =>\n Submodel.defineView<EditorModel<FormModel>, Message, ViewInputs>(((\n model: EditorModel<FormModel>,\n inputs: ViewInputs,\n h: HtmlBuilder<Message>,\n ): Html =>\n (view as (model: FormModel, inputs: ViewInputs, h: HtmlBuilder<Message>) => Html)(\n model.form,\n inputs,\n h,\n )) as never),\n\n /**\n * The choices of every relation picker in a form, from the lists of their\n * targets: `foldkit-mixins-form`'s `options`, keyed by the form's keys. A\n * picker whose target no list here is over is a wiring mistake, reported now.\n *\n * With `chosen`, the form's Model as the page has it, what a picker already\n * holds stays among its choices when the list no longer finds it (a search\n * narrowed it, or it is on a later page), and `active` requires those rows so\n * their words are there. Give `chosen` to any form whose pickers search.\n */\n options: <Key extends string, Root, FormModel = never>(\n form: {\n readonly controls: ReadonlyArray<FormControl<Key>>\n readonly field?: (model: FormModel, key: never) => { readonly value: unknown }\n },\n lists: ReadonlyArray<{\n readonly name: string\n readonly entity: AnyEntity\n readonly offersChoices: boolean\n readonly owner: object\n readonly choices: (root: Root) => ReadonlyArray<Choice>\n readonly row: (id: string) => Projection<Root, RemoteData<any>>\n readonly choiceOf: (root: Root, id: string) => Choice | undefined\n readonly more: (root: Root) => Option.Option<Root>\n readonly hasMore: (root: Root) => boolean\n }>,\n options: { readonly chosen?: (root: Root) => FormModel | undefined } = {},\n ) => {\n const pickers = form.controls.flatMap(({ key, control }) => {\n if (!Input.RelationOne.is(control) && !Input.RelationMany.is(control)) return []\n const { target } = control.data\n const list = lists.find(candidate => Entity.same(candidate.entity, target))\n if (list === undefined)\n throw new Error(\n `Crud.options: \"${key}\" picks a ${target.name}, and no list given is over ${target.name}`,\n )\n if (!list.offersChoices)\n throw new Error(\n `Crud.options: \"${key}\" would pick from list \"${list.name}\", which has no \"choice\"`,\n )\n return [[key, list] as const]\n })\n\n /** The ids each picker holds now: the draft of a `one`, or of a `many`. */\n const held = (root: Root): ReadonlyArray<readonly [Key, (typeof lists)[number], string]> => {\n const model = options.chosen?.(root)\n const field = form.field as\n | ((model: FormModel, key: string) => { readonly value: unknown })\n | undefined\n if (model === undefined || field === undefined) return []\n return pickers.flatMap(([key, list]) => {\n const draft = field(model, key).value\n const ids = Array.isArray(draft) ? draft : typeof draft === 'string' ? [draft] : []\n return ids\n .filter((id): id is string => typeof id === 'string' && id !== '')\n .map(id => [key, list, id] as const)\n })\n }\n\n const choices = (root: Root): { readonly [K in Key]?: ReadonlyArray<Choice> } => {\n const chosen = held(root)\n return Object.fromEntries(\n pickers.map(([key, list]) => {\n const listed = list.choices(root)\n const missing = chosen\n .filter(([heldBy, , id]) => heldBy === key && !listed.some(item => item.value === id))\n .flatMap(([, , id]) => list.choiceOf(root, id) ?? [])\n // What is chosen leads, so it is in reach whatever the list shows.\n return [key, [...missing, ...listed]]\n }),\n ) as unknown as { readonly [K in Key]?: ReadonlyArray<Choice> }\n }\n\n const [first] = lists\n if (first === undefined)\n throw new Error(\n 'Crud.options: give it the lists its pickers pick from; with none, it has nothing to read',\n )\n const listOf = new Map<string, (typeof lists)[number]>(pickers)\n return Object.assign(choices, {\n /** For `FormView`'s `more`: the pickers whose list has more rows than it shows. */\n more: (root: Root): { readonly [K in Key]?: boolean } =>\n Object.fromEntries(\n pickers.flatMap(([key, list]) => (list.hasMore(root) ? [[key, true]] : [])),\n ) as { readonly [K in Key]?: boolean },\n /**\n * The page with one more page of `key`'s list, for the form's `AskedForMore`\n * (`Crud.screen` answers it given these pickers); the page as it is for a key\n * that is no picker here, or a list that shows every row.\n */\n ask: (root: Root, key: Key): Root =>\n Option.getOrElse(\n Option.flatMap(Option.fromNullishOr(listOf.get(key)), list => list.more(root)),\n () => root,\n ),\n /** For `Data.subscriptions`: what the pickers hold is read, so it can be named. */\n active: {\n name: `${pickers.map(([key]) => key).join('+')} chosen`,\n owner: first.owner,\n projectionOf: (root: Root) => {\n const rows = held(root)\n return rows.length === 0\n ? Option.none()\n : Option.some(\n Projection.struct(\n Object.fromEntries(rows.map(([key, list, id]) => [`${key}:${id}`, list.row(id)])),\n ),\n )\n },\n messages: [],\n } satisfies ActiveSurface<Root>,\n })\n },\n\n /**\n * What the pieces on a page require of Remote, by the names given: a placed\n * editor, list, or detail, or the pickers from `Crud.options`. It is each one's\n * `active`, gathered, so handing the pieces over is enough and none is left out:\n *\n * ```ts\n * Data.wiring(Crud.actives({ posts: Posts, authors: Authors, editor: PostEditor, pickers }))\n * ```\n */\n actives: <const Pieces extends Readonly<Record<string, { readonly active: unknown }>>>(\n pieces: Pieces,\n ): { readonly [K in keyof Pieces]: Pieces[K]['active'] } =>\n Object.fromEntries(Object.entries(pieces).map(([name, piece]) => [name, piece.active])) as {\n readonly [K in keyof Pieces]: Pieces[K]['active']\n },\n\n /**\n * An editor for one form and the mutation its value feeds. The form's value\n * must be the mutation's input, which is what declaring the input once and\n * giving it to both guarantees.\n */\n editor: <\n const Name extends string,\n FormName extends string,\n FormModel,\n FormMessage extends { readonly _tag: string },\n Value,\n E extends AnyEntity,\n Fields extends Schema.Struct.Fields,\n Members,\n R = never,\n >(\n name: Name,\n config: {\n readonly form: EditableForm<FormName, FormModel, FormMessage, Value, E, Fields, Members, R>\n readonly mutation: MutationDescriptor<string, Value, any>\n /**\n * Which of the form's keys the row being edited does not let its reader\n * set, from members loaded beside the form's: `foldkit-guard`'s\n * `May.locks(Todo)`. Read with `locked`.\n */\n readonly locks?: Locks | undefined\n },\n ) => {\n type Model = EditorModel<FormModel>\n const { form, mutation, locks } = config\n // How each input key's value is compared, built once from its own schema.\n const sameValue: Readonly<Record<string, (left: unknown, right: unknown) => boolean>> =\n Object.fromEntries(\n Object.entries(form.input.schema.fields).map(([key, schema]) => [\n key,\n Schema.toEquivalence(schema as Schema.Codec<unknown, unknown>),\n ]),\n )\n // The input key holding the revision a declared write expects, when it expects one.\n const expectKey = mutation.write?.expect?.key\n // The keys a refusal can name: a nested key holds rows, not a draft to mark.\n const draftKeys = new Set(\n form.controls.filter(entry => !Input.Nested.is(entry.control)).map(entry => entry.key),\n )\n // The form's key that sets each field, for an invariant's path, which names fields.\n const keyOfField = new Map<string, string>(\n Object.entries(form.input.members as Readonly<Record<string, InputMember>>).flatMap(\n ([key, member]) => {\n if (member._tag !== 'Field' || !draftKeys.has(key)) return []\n return [[member.key, key] as const]\n },\n ),\n )\n /** The keys a refusal names among the form's, each with what to say beside it. */\n const namedBy = (\n refusal: unknown,\n ): ReadonlyArray<{ readonly key: string; readonly error: string }> => {\n if (Refusal.isField(refusal)) {\n return draftKeys.has(refusal.key)\n ? [{ key: refusal.key, error: String(refusal.reason) }]\n : []\n }\n if (Refusal.isInvalid(refusal)) {\n return refusal.path.flatMap(field =>\n Option.toArray(Option.fromUndefinedOr(keyOfField.get(field))).map(key => ({\n key,\n error: refusal.message,\n })),\n )\n }\n return []\n }\n\n const closed: Model = {\n form: form.initial,\n mode: 'closed',\n target: null,\n filled: false,\n requestId: null,\n edited: false,\n sent: Option.none(),\n settledFor: Option.none(),\n refusedFor: Option.none(),\n filledWith: Option.none(),\n }\n const Model = Schema.Struct({\n form: form.bundle.Model,\n mode: Schema.Literals(['closed', 'new', 'edit']),\n target: Schema.NullOr(Schema.String),\n filled: Schema.Boolean,\n requestId: Schema.NullOr(Schema.String),\n edited: Schema.Boolean,\n sent: Schema.OptionFromNullOr(Schema.Record(Schema.String, Schema.Unknown)),\n settledFor: Schema.OptionFromNullOr(Schema.String),\n refusedFor: Schema.OptionFromNullOr(Schema.String),\n filledWith: Schema.OptionFromNullOr(Schema.Record(Schema.String, Schema.Unknown)),\n }) as unknown as Schema.Codec<Model, unknown>\n\n // The editor's Messages are the form's own: it adds state around the form,\n // not a second vocabulary.\n const bundle = Bundle.make(name, {\n Model,\n Message: form.bundle.Message,\n init: () => ({ model: closed }),\n update: (model: Model, message: FormMessage) => {\n const next = form.bundle.update(model.form, message, undefined)\n // An edit after a save starts a new round: the last save no longer\n // describes the form, though its answer is still the form's to take.\n // Any authored change counts, not only `Changed`: a row added or a\n // Bundle-backed control's edit is one too.\n const edited = model.edited || form.authoredChanged(model.form, next.model)\n const unchanged = next.model === model.form && edited === model.edited\n // The form's Commands are the editor's: a check the form started has to run.\n const step = {\n model: unchanged ? model : { ...model, form: next.model, edited },\n ...(next.commands === undefined ? {} : { commands: next.commands }),\n }\n return next.outMessage === undefined ? step : { ...step, outMessage: next.outMessage }\n },\n helpers: {\n /** Edits `id`: the form is emptied, and filled once the current values arrive. */\n open: (_: Model, id: IdOf<E>) => ({\n model: { ...closed, mode: 'edit' as const, target: id },\n }),\n /** A blank form for a new one: nothing to load. */\n blank: () => ({ model: { ...closed, mode: 'new' as const, filled: true } }),\n close: () => ({ model: closed }),\n },\n })\n\n const written = Entity.selectFor(form.input)\n // What is loaded: what the form writes, and what `locks` reads beside it,\n // which the form never fills from.\n const current: typeof written =\n locks === undefined\n ? written\n : (Entity.select(form.input.entity, {\n ...(written.members as Readonly<Record<string, unknown>>),\n ...locks.select,\n } as never) as typeof written)\n /** The member each form key writes: a field's own key, or the relation it points. */\n const writes: ReadonlyArray<readonly [keyof Fields & string, string]> = Object.entries(\n form.input.members as Readonly<Record<string, InputMember | undefined>>,\n ).flatMap(([key, member]) =>\n member === undefined\n ? []\n : Match.valueTags(member, {\n Field: ({ key: written }) => [[key as keyof Fields & string, written] as const],\n RelationInput: ({ relation }) => [\n [key as keyof Fields & string, relation.key] as const,\n ],\n NestedInput: () => [],\n Unmapped: () => [],\n }),\n )\n\n return {\n bundle,\n /** What the editor loads before it shows the form: every member the form writes. */\n selection: current,\n\n /**\n * The editor where it lives: `model` is its slice of the parent Model (the\n * field it was declared at), and `data` the Remote domain that registers the\n * mutation and the Entity.\n */\n at: <Root>(where: {\n readonly data: DomainLike<Root>\n readonly model: ModelRef<Root, Model>\n }) => {\n const { data, model: slice } = where\n const loaded = (root: Root): RemoteData<any> | undefined => {\n const { mode, target } = slice.get(root)\n return mode === 'edit' && target !== null\n ? data.get(current, target).read(root)\n : undefined\n }\n\n /**\n * Shows the loaded value once, the first time it is there. Later refreshes\n * leave the drafts alone. A value whose latest refresh failed is still the\n * last one known good, so it fills the form too: `status` goes on saying\n * `Editing`, and the failure is the read's to show.\n */\n /**\n * A save the server refused for the form's keys shows on those keys,\n * once per save: `Refusal.field`'s reason, as text, or `Refusal.invalid`'s\n * message beside each key its path names. Any other refusal, and one\n * naming no key of the form, is the status's to say.\n */\n const refused = (root: Root): Root => {\n const editor = slice.get(root)\n const { requestId } = editor\n if (requestId === null || Option.contains(editor.refusedFor, requestId)) return root\n const named = Option.match(refusalOf(root), { onNone: () => [], onSome: namedBy })\n if (named.length === 0) return root\n return slice.set(root, {\n ...editor,\n form: named.reduce(\n (shown, { key, error }) =>\n form.bundle.update(shown, form.Message.Refused({ key, error }), undefined).model,\n editor.form,\n ),\n refusedFor: Option.some(requestId),\n })\n }\n\n /** The value loaded, or the last one known good when a refresh failed. */\n const known = (root: Root): Option.Option<any> => {\n const read = loaded(root)\n return read === undefined\n ? Option.none()\n : RemoteData.match(read, {\n Initial: () => Option.none(),\n Loading: () => Option.none(),\n Ready: Option.some,\n Refreshing: Option.some,\n Failed: (_, previous) => previous,\n NotFound: () => Option.none(),\n })\n }\n\n const filledOf = (root: Root): Root => {\n const editor = slice.get(root)\n if (editor.filled) return root\n const shown = known(root)\n if (Option.isNone(shown)) return root\n const value = shown.value\n const values = Entity.valuesFor(form.input, value) as Partial<Value>\n return slice.set(root, {\n ...editor,\n form: form.fill(editor.form, values).model,\n filled: true,\n filledWith: Option.some(values),\n })\n }\n\n /**\n * The input keys a save changes from what the form was filled with, by\n * each key's own schema: none filled (something new) is every key, so\n * none are named.\n */\n const changedKeys = (editor: Model, value: Value): ReadonlyArray<string> | undefined => {\n // A submitted value is the form's decoded struct, so it reads by key.\n const submitted = value as Readonly<Record<string, unknown>>\n return Option.match(editor.filledWith, {\n onNone: () => undefined,\n onSome: filled =>\n Object.keys(submitted).filter(\n key => !Object.hasOwn(filled, key) || !sameValue[key]!(filled[key], submitted[key]),\n ),\n })\n }\n\n /** The revision the row is at now, as the form's `expect` key holds it. */\n const currentRevision = (root: Root): Option.Option<unknown> => {\n const read = loaded(root)\n if (expectKey === undefined || read === undefined) return Option.none()\n if (read._tag !== 'Ready' && read._tag !== 'Refreshing') return Option.none()\n const values = Entity.valuesFor(form.input, read.value) as Readonly<\n Record<string, unknown>\n >\n return Object.hasOwn(values, expectKey) ? Option.some(values[expectKey]) : Option.none()\n }\n /** Whether the row is at another revision than the one the form was filled with. */\n const moved = (root: Root): boolean => {\n const { filledWith } = slice.get(root)\n return (\n expectKey !== undefined &&\n Option.isSome(filledWith) &&\n Option.exists(\n currentRevision(root),\n revision => !sameValue[expectKey]!(filledWith.value[expectKey], revision),\n )\n )\n }\n\n /**\n * Once this editor's save lands, the form takes its answer, once: what\n * it sent is now what the row holds as the form knows it, and the\n * revision is the one it moved the row to, or its next save would\n * conflict with itself. A revision moved after that is another's, and\n * reads as `Moved`. A create's form starts blank again, its values now\n * the row's; one the author went on typing in is a new one's draft.\n */\n const settled = (root: Root): Root => {\n const editor = slice.get(root)\n const { requestId } = editor\n if (requestId === null || Option.contains(editor.settledFor, requestId)) return root\n if (saveOf(root)._tag !== 'Applied') return root\n const settledFor = Option.some(requestId)\n if (editor.mode === 'new') {\n return slice.set(root, {\n ...editor,\n settledFor,\n ...(editor.edited ? {} : { form: form.initial }),\n })\n }\n const revision = expectKey === undefined ? Option.none() : currentRevision(root)\n const filledWith = Option.some({\n ...Option.getOrElse(editor.filledWith, () => ({})),\n ...Option.getOrElse(editor.sent, () => ({})),\n ...Option.match(revision, {\n onNone: () => ({}),\n onSome: value => ({ [expectKey!]: value }),\n }),\n })\n return slice.set(root, {\n ...editor,\n settledFor,\n filledWith,\n form: Option.match(revision, {\n onNone: () => editor.form,\n onSome: value =>\n form.fill(editor.form, { [expectKey!]: value } as Partial<Value>).model,\n }),\n })\n }\n\n const sync: Update.Step<Root, never, never> = root => ({\n model: settled(refused(filledOf(root))),\n })\n\n /** Why the server refused the last save, when it refused it as data. */\n const refusalOf = (root: Root): Option.Option<unknown> => {\n const { requestId } = slice.get(root)\n return requestId === null ? Option.none() : data.refusal(root, requestId, mutation)\n }\n\n const saveOf = (root: Root): MutationStatus => {\n const { requestId } = slice.get(root)\n return requestId === null ? { _tag: 'Unknown' } : data.mutation(root, requestId)\n }\n\n return {\n /** For the placement: a valid submit becomes the mutation, and its request is remembered. */\n onOut:\n (submitted: Submitted<Value>): Step<Root> =>\n root => {\n // A declared write is told what changed, so it writes, and shows, only that.\n const started = data.mutate(\n root,\n mutation,\n submitted.value,\n mutation.write === undefined\n ? undefined\n : { keys: changedKeys(slice.get(root), submitted.value) },\n )\n const editor = slice.get(started.model)\n return {\n model: slice.set(started.model, {\n ...editor,\n requestId: started.requestId,\n edited: false,\n sent: Option.some(submitted.value as Readonly<Record<string, unknown>>),\n }),\n commands: [started.command],\n }\n },\n\n /**\n * For `Data.subscriptions`: while an id is being edited, what it loads is\n * a requirement like any Surface's, so Remote fetches and retains it.\n */\n active: data.active(name, root => {\n const { mode, target } = slice.get(root)\n return mode === 'edit' && target !== null\n ? Option.some(data.get(current, target))\n : Option.none()\n }),\n\n sync,\n\n /**\n * Wraps the application's `update` so `sync` runs after every Message:\n * the value may arrive with any of them.\n */\n after:\n <Message, Result extends { readonly model: Root }>(\n update: (model: Root, message: Message) => Result,\n ) =>\n (model: Root, message: Message): Result => {\n const next = update(model, message)\n return { ...next, model: sync(next.model).model }\n },\n\n /**\n * Asks for the value being edited again: `Data.refresh`, for a retry\n * button while `status` is `LoadFailed`. A failed read is not retried on\n * its own. Unchanged while nothing is open for editing.\n */\n refresh: (root: Root): Root => {\n const { mode, target } = slice.get(root)\n return mode === 'edit' && target !== null\n ? data.refresh(root, data.get(current, target))\n : root\n },\n\n /** Why the last save failed, while `status` is `SaveFailed`. */\n saveError: (root: Root): RemoteError | undefined => {\n const save = saveOf(root)\n return save._tag === 'Failed' ? save.error : undefined\n },\n\n /**\n * The form's keys the row being edited does not let its reader set, by\n * `locks`, for `foldkit-mixins-form`'s `locked`. None for something new,\n * whose create the server checks, and none until the row is loaded.\n */\n locked: (root: Root): ReadonlyArray<keyof Fields & string> =>\n locks === undefined\n ? []\n : Option.match(known(root), {\n onNone: () => [],\n onSome: row =>\n writes.flatMap(([key, member]) => (locks.locked(row, member) ? [key] : [])),\n }),\n\n /** The id being edited, typed as the Entity's own; `null` for a new one or a closed editor. */\n target: (root: Root): IdOf<E> | null => slice.get(root).target as IdOf<E> | null,\n status: (root: Root): EditorStatus => {\n const editor = slice.get(root)\n if (editor.mode === 'closed') return 'Closed'\n // Gone outranks everything: a save that landed describes a thing that no longer exists.\n const read = loaded(root)\n if (read?._tag === 'NotFound') return 'NotFound'\n const save = saveOf(root)\n // Changed since the last save: that save no longer describes the form.\n if (editor.edited) return moved(root) ? 'Moved' : 'Editing'\n switch (save._tag) {\n case 'Pending':\n return 'Saving'\n case 'Failed':\n return Option.exists(refusalOf(root), Refusal.isConflict)\n ? 'Conflict'\n : 'SaveFailed'\n case 'Applied':\n // Saved, and since moved on by someone else.\n return moved(root) ? 'Moved' : 'Saved'\n case 'Unknown':\n if (editor.filled) return moved(root) ? 'Moved' : 'Editing'\n return read?._tag === 'Failed' ? 'LoadFailed' : 'Loading'\n }\n },\n }\n },\n }\n },\n}\n\nexport { Display, type DisplayColumn, type DisplayKind, type DisplayWords } from './display.js'\nexport { Sort, type SortState, type SortedColumn } from './sort.js'\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foldkit-crud",
3
- "version": "0.9.0",
3
+ "version": "0.10.0",
4
4
  "description": "Create, read, update and delete screens for Foldkit Plus, assembled from a form, a Remote operation, and the Entity they share. Each capability is declared, never implied.",
5
5
  "keywords": [
6
6
  "foldkit",
@@ -40,11 +40,11 @@
40
40
  "LICENSE"
41
41
  ],
42
42
  "dependencies": {
43
- "foldkit-entity": "0.11.0",
44
43
  "foldkit-bundle": "0.8.1",
45
- "foldkit-form": "0.7.0",
44
+ "foldkit-form": "0.8.0",
46
45
  "foldkit-metadata": "0.1.0",
47
- "foldkit-remote": "0.19.0",
46
+ "foldkit-remote": "0.20.0",
47
+ "foldkit-entity": "0.12.0",
48
48
  "foldkit-surface": "0.10.2"
49
49
  },
50
50
  "peerDependencies": {