@elaraai/e3-ui 1.0.82 → 1.0.84

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/dist/src/bind/record.d.ts +229 -4
  2. package/dist/src/bind/record.d.ts.map +1 -1
  3. package/dist/src/bind/record.js +245 -5
  4. package/dist/src/bind/record.js.map +1 -1
  5. package/dist/src/index.d.ts +2 -1
  6. package/dist/src/index.d.ts.map +1 -1
  7. package/dist/src/index.js +4 -1
  8. package/dist/src/index.js.map +1 -1
  9. package/dist/src/internal.d.ts +2 -1
  10. package/dist/src/internal.d.ts.map +1 -1
  11. package/dist/src/internal.js +5 -1
  12. package/dist/src/internal.js.map +1 -1
  13. package/dist/src/studio/builder.d.ts +3123 -0
  14. package/dist/src/studio/builder.d.ts.map +1 -0
  15. package/dist/src/studio/builder.js +115 -0
  16. package/dist/src/studio/builder.js.map +1 -0
  17. package/dist/src/studio/canvas.d.ts +83 -0
  18. package/dist/src/studio/canvas.d.ts.map +1 -0
  19. package/dist/src/studio/canvas.js +71 -0
  20. package/dist/src/studio/canvas.js.map +1 -0
  21. package/dist/src/studio/component.d.ts +178 -0
  22. package/dist/src/studio/component.d.ts.map +1 -0
  23. package/dist/src/studio/component.js +189 -0
  24. package/dist/src/studio/component.js.map +1 -0
  25. package/dist/src/studio/index.d.ts +104 -0
  26. package/dist/src/studio/index.d.ts.map +1 -0
  27. package/dist/src/studio/index.js +75 -0
  28. package/dist/src/studio/index.js.map +1 -0
  29. package/dist/src/studio/inspector.d.ts +208 -0
  30. package/dist/src/studio/inspector.d.ts.map +1 -0
  31. package/dist/src/studio/inspector.js +155 -0
  32. package/dist/src/studio/inspector.js.map +1 -0
  33. package/dist/src/studio/library.d.ts +3506 -0
  34. package/dist/src/studio/library.d.ts.map +1 -0
  35. package/dist/src/studio/library.js +245 -0
  36. package/dist/src/studio/library.js.map +1 -0
  37. package/dist/src/studio/page.d.ts +305 -0
  38. package/dist/src/studio/page.d.ts.map +1 -0
  39. package/dist/src/studio/page.js +105 -0
  40. package/dist/src/studio/page.js.map +1 -0
  41. package/dist/src/studio/pages.d.ts +10929 -0
  42. package/dist/src/studio/pages.d.ts.map +1 -0
  43. package/dist/src/studio/pages.js +502 -0
  44. package/dist/src/studio/pages.js.map +1 -0
  45. package/dist/src/studio/palette.d.ts +179 -0
  46. package/dist/src/studio/palette.d.ts.map +1 -0
  47. package/dist/src/studio/palette.js +120 -0
  48. package/dist/src/studio/palette.js.map +1 -0
  49. package/dist/src/studio/publish.d.ts +397 -0
  50. package/dist/src/studio/publish.d.ts.map +1 -0
  51. package/dist/src/studio/publish.js +198 -0
  52. package/dist/src/studio/publish.js.map +1 -0
  53. package/dist/test/bind/record/record.examples.d.ts +65 -1
  54. package/dist/test/bind/record/record.examples.d.ts.map +1 -1
  55. package/dist/test/bind/record/record.examples.js +21 -3
  56. package/dist/test/bind/record/record.examples.js.map +1 -1
  57. package/dist/test/studio/component.examples.d.ts +7 -0
  58. package/dist/test/studio/component.examples.d.ts.map +1 -0
  59. package/dist/test/studio/component.examples.js +46 -0
  60. package/dist/test/studio/component.examples.js.map +1 -0
  61. package/dist/test/studio/page.examples.d.ts +7 -0
  62. package/dist/test/studio/page.examples.d.ts.map +1 -0
  63. package/dist/test/studio/page.examples.js +64 -0
  64. package/dist/test/studio/page.examples.js.map +1 -0
  65. package/dist/test/studio/studio.examples.d.ts +1766 -0
  66. package/dist/test/studio/studio.examples.d.ts.map +1 -0
  67. package/dist/test/studio/studio.examples.js +354 -0
  68. package/dist/test/studio/studio.examples.js.map +1 -0
  69. package/package.json +9 -8
@@ -15,10 +15,16 @@
15
15
  * latest-wins mutation lifecycle. A record's mutations serialize server-side
16
16
  * under compare-and-swap, so one shared channel per record is the honest model.
17
17
  *
18
+ * `commit.<name>(…)` is the same write awaited: an async closure that resolves
19
+ * to the server's terminal outcome. `Record.onApply` builds on it — an
20
+ * editable collection's `onApply` that commits each batch to the record
21
+ * through its patch door, as one commit.
22
+ *
18
23
  * @packageDocumentation
19
24
  */
20
- import { ArrayType, BooleanType, FunctionType, IntegerType, NullType, OptionType, StringType, StructType, VariantType, type EastType, type ExprType } from '@elaraai/east';
25
+ import { ArrayType, AsyncFunctionType, BooleanType, DictType, FunctionType, IntegerType, NullType, OptionType, StringType, StructType, VariantType, type EastType, type ExprType, type SubtypeExprOrValue } from '@elaraai/east';
21
26
  import { DatasetStatusType, RecordCommitInfoType } from '@elaraai/e3-types';
27
+ import { Editing } from '@elaraai/east-ui';
22
28
  import type { RecordDef, MutationDef } from '@elaraai/e3';
23
29
  /**
24
30
  * Shared mutate-lifecycle tag for a bound record. One launch channel per
@@ -93,11 +99,56 @@ export declare const RecordBindingType: StructType<{
93
99
  }>;
94
100
  /** Type representing the {@link RecordBindingType} structure. */
95
101
  export type RecordBindingType = typeof RecordBindingType;
102
+ /**
103
+ * The outcome of an awaited write (`commit.<name>`): the server's terminal
104
+ * `MutationResult`, passed on as it came, and `transport` for a call that got
105
+ * no answer.
106
+ *
107
+ * @remarks
108
+ * Only `committed` wrote anything. `transport` is the one outcome that leaves
109
+ * it unknown whether the write committed — it may have reached the server
110
+ * before its answer was lost — which is what the write's request id is for: a
111
+ * retry with the same id resolves to that commit instead of writing twice.
112
+ *
113
+ * @property committed - The new commit, and the record state it wrote
114
+ * @property invalid - A lookup, arity or signature error; nothing ran
115
+ * @property failed - The mutation's program failed; see its stderr
116
+ * @property timed_out - The program ran out of time; nothing was written
117
+ * @property conflict - The compare-and-swap lost the race `attempts` times, or
118
+ * the write no longer matched the record — then `detail` names the key
119
+ * @property transport - The call got no answer, so whether it committed is unknown
120
+ */
121
+ export declare const RecordOutcomeType: VariantType<{
122
+ readonly transport: StructType<{
123
+ readonly message: StringType;
124
+ }>;
125
+ readonly committed: StructType<{
126
+ readonly commitHash: StringType;
127
+ readonly stateHash: StringType;
128
+ }>;
129
+ readonly invalid: StructType<{
130
+ readonly message: StringType;
131
+ }>;
132
+ readonly failed: StructType<{
133
+ readonly exitCode: IntegerType;
134
+ readonly stderr: StringType;
135
+ }>;
136
+ readonly timed_out: StructType<{
137
+ readonly ms: IntegerType;
138
+ readonly stderr: StringType;
139
+ }>;
140
+ readonly conflict: StructType<{
141
+ readonly attempts: IntegerType;
142
+ readonly detail: OptionType<StringType>;
143
+ }>;
144
+ }>;
145
+ /** Type representing the {@link RecordOutcomeType} structure. */
146
+ export type RecordOutcomeType = typeof RecordOutcomeType;
96
147
  /**
97
148
  * The struct returned by every {@link Record.bind} call. The read side
98
149
  * (`read` / `status` / `history`) is served reactively from the shared dataset
99
150
  * cache; the write side lives under `mutate` — one closure per mutation plus
100
- * the shared lifecycle accessors.
151
+ * the shared lifecycle accessors — and under `commit`, the same writes awaited.
101
152
  *
102
153
  * @remarks
103
154
  * The handle's shape IS the signature: the runtime recovers the state type
@@ -118,6 +169,14 @@ export type RecordBindingType = typeof RecordBindingType;
118
169
  * @property mutate - The write surface: one fire-and-forget closure per
119
170
  * mutation (typed from its def), plus the shared `pending` / `status` /
120
171
  * `error` / `cancel`.
172
+ * @property commit - The writes awaited: one async closure per mutation,
173
+ * taking a request id before the mutation's own arguments and resolving to
174
+ * its {@link RecordOutcomeType}. The id makes a retry after an unanswered
175
+ * call resolve to the first call's commit rather than writing twice; `""`
176
+ * is a write without one. It runs on `mutate`'s channel, so `pending` /
177
+ * `status` / `error` show it too, and a conflict reads the record and its
178
+ * history again before the outcome settles — `read` and `history` then show
179
+ * what the write lost to.
121
180
  * @property start - Launch the workspace dataflow without mutating, so tasks
122
181
  * that consume this record recompute against its latest committed state.
123
182
  * The standalone "Run" affordance — a mutation refreshes the record's own
@@ -142,6 +201,7 @@ export declare const RecordBindHandleType: <T extends EastType, M extends Record
142
201
  readonly delta: OptionType<StringType>;
143
202
  }>>>>;
144
203
  readonly mutate: StructType<Record<string, EastType>>;
204
+ readonly commit: StructType<Record<string, EastType>>;
145
205
  readonly start: FunctionType<[], NullType>;
146
206
  readonly binding: StructType<{
147
207
  readonly name: StringType;
@@ -172,6 +232,9 @@ type RecordHandle<T extends EastType, M extends Record<string, EastType[]>> = St
172
232
  } & {
173
233
  [K in keyof M]: FunctionType<M[K], typeof NullType>;
174
234
  }>;
235
+ commit: StructType<{
236
+ [K in keyof M]: AsyncFunctionType<[typeof StringType, ...M[K]], RecordOutcomeType>;
237
+ }>;
175
238
  start: FunctionType<[], typeof NullType>;
176
239
  binding: RecordBindingType;
177
240
  }>;
@@ -268,6 +331,31 @@ export declare const RecordBindPrimitives: {
268
331
  readonly mutateCancel: import("@elaraai/east").PlatformDefinition<[StringType], NullType>;
269
332
  /** `record_mutate([ArgsStruct], recordName, mutationName, argsStruct) -> Null`. */
270
333
  readonly mutate: import("@elaraai/east").GenericPlatformDefinition<readonly ["A"], readonly [StringType, StringType, "A"], NullType>;
334
+ /** `record_commit([ArgsStruct], recordName, mutationName, requestId, argsStruct) ~> RecordOutcome` — the awaited write. */
335
+ readonly commit: import("@elaraai/east").AsyncGenericPlatformDefinition<readonly ["A"], readonly [StringType, StringType, StringType, "A"], VariantType<{
336
+ readonly transport: StructType<{
337
+ readonly message: StringType;
338
+ }>;
339
+ readonly committed: StructType<{
340
+ readonly commitHash: StringType;
341
+ readonly stateHash: StringType;
342
+ }>;
343
+ readonly invalid: StructType<{
344
+ readonly message: StringType;
345
+ }>;
346
+ readonly failed: StructType<{
347
+ readonly exitCode: IntegerType;
348
+ readonly stderr: StringType;
349
+ }>;
350
+ readonly timed_out: StructType<{
351
+ readonly ms: IntegerType;
352
+ readonly stderr: StringType;
353
+ }>;
354
+ readonly conflict: StructType<{
355
+ readonly attempts: IntegerType;
356
+ readonly detail: OptionType<StringType>;
357
+ }>;
358
+ }>>;
271
359
  };
272
360
  /**
273
361
  * Bind an `e3.record` and its mutations to a reactive read/mutate handle.
@@ -317,21 +405,158 @@ export declare const RecordBindPrimitives: {
317
405
  * ```
318
406
  */
319
407
  declare function bindRecord<T extends EastType, Muts extends readonly MutationDef<string, T, EastType[]>[]>(record: RecordDef<T>, mutations: [...Muts]): BoundRecord<T, Muts>;
408
+ /**
409
+ * Options for {@link Record.onApply} over the record's own entries — a Sheet or
410
+ * a Plan whose entries are the record's, each named by its key's text.
411
+ */
412
+ export interface RecordApplyOptions {
413
+ /** The patch mutation each Apply commits through — the name
414
+ * `e3.mutation.patch(record)` was given; `patch` when omitted. */
415
+ mutation?: string;
416
+ /** The collection hands keyed batches, `ChangeSet(V, K)`, as the Plan does;
417
+ * a Sheet hands `ChangeSet(V)`, the default. */
418
+ keyed?: boolean;
419
+ }
420
+ /**
421
+ * Options for {@link Record.onApply} over a collection inside one entry — the
422
+ * rows of one Array field of one entry, which each Apply's patch reaches and
423
+ * nothing else.
424
+ *
425
+ * @typeParam K - The record's key type
426
+ * @typeParam V - The record's entry type
427
+ * @typeParam C - The collection's row type
428
+ */
429
+ export interface RecordApplyInsideOptions<K extends EastType, V extends EastType, C extends StructType> {
430
+ /** The key of the entry the collection lives in. */
431
+ entry: SubtypeExprOrValue<K>;
432
+ /** The collection, read out of the entry. */
433
+ get: SubtypeExprOrValue<FunctionType<[V], ArrayType<C>>>;
434
+ /** The entry with its collection replaced, and nothing else changed. */
435
+ set: SubtypeExprOrValue<FunctionType<[V, ArrayType<C>], V>>;
436
+ /** The rows' String identity field. */
437
+ idField: string;
438
+ /** The patch mutation each Apply commits through; `patch` when omitted. */
439
+ mutation?: string;
440
+ }
441
+ /** A bound record whose state is keyed, `Dict<K, V>` — what an Apply writes by key. */
442
+ type KeyedRecordHandle<K extends EastType, V extends EastType> = ExprType<StructType<{
443
+ read: FunctionType<[], DictType<K, V>>;
444
+ }>>;
445
+ /** An editable collection's async `onApply`, over the batch type it hands. */
446
+ type RecordApplyFunction<B extends EastType> = ExprType<AsyncFunctionType<[B], typeof Editing.Types.ApplyResult>>;
447
+ /**
448
+ * Commit an editable collection's batches to an `e3.record` — the `onApply` a
449
+ * Sheet, a Plan or a SnapGrid takes, each Apply one commit through the record's
450
+ * patch door (`e3.mutation.patch`).
451
+ *
452
+ * @remarks
453
+ * Two forms:
454
+ * - **Over the record's own entries** (`Record.onApply(handle)`): the
455
+ * collection's entries are the record's, each change naming its entry by
456
+ * the key's text — a String key as it is, any other its `.east` printing.
457
+ * Each change is its entry's insert, update or delete: the batch's own
458
+ * entry patches, restated by key.
459
+ * - **Over a collection inside one entry** (`{ entry, get, set, idField }`):
460
+ * the rows of one entry's Array field. The batch is applied to the rows it
461
+ * began from, and the patch is the diff of that entry before and after —
462
+ * it reaches the rows and nothing else.
463
+ *
464
+ * Every patch carries what the entries were when the edit began, and the
465
+ * record checks it: an entry another write moved is a `conflict` naming it
466
+ * and who changed the record last, and nothing is overwritten. A commit is
467
+ * `applied` at the state it wrote; an invalid, failed or timed-out write is
468
+ * `rejected`. A write that got no answer throws, and the session's retry
469
+ * resolves to its commit, if it made one, through the batch's request id.
470
+ *
471
+ * The handle must be bound with the patch mutation — `Record.bind(record,
472
+ * [e3.mutation.patch(record)])` — and the record must be a `Dict`.
473
+ *
474
+ * @typeParam K - The record's key type
475
+ * @typeParam V - The record's entry type
476
+ * @param handle - The record, bound with its patch mutation
477
+ * @param options - {@link RecordApplyOptions} for the entries form, or
478
+ * {@link RecordApplyInsideOptions} for a collection inside one entry
479
+ * @returns An async East function over the collection's batch, answering with
480
+ * `Editing.Types.ApplyResult`
481
+ * @throws {Error} When the record is not a Dict, the mutation is not bound as
482
+ * its patch door, or `get` / `set` do not fit the entry
483
+ *
484
+ * @example
485
+ * ```tsx
486
+ * import { DictType, East, IntegerType, StringType, StructType } from "@elaraai/east";
487
+ * import { Reactive, Sheet, UIComponentType } from "@elaraai/east-ui";
488
+ * import { Data, Record } from "@elaraai/e3-ui";
489
+ * import e3 from "@elaraai/e3";
490
+ *
491
+ * const JobType = StructType({ task: StringType, qty: IntegerType });
492
+ * const jobs = e3.record("jobs", DictType(StringType, JobType), new Map());
493
+ * const jobsPatch = e3.mutation.patch(jobs);
494
+ *
495
+ * // Mirrors `recordSheetApply` in test/bind/record/record.examples.tsx.
496
+ * const jobSheet = East.function([], UIComponentType, (_$) => (
497
+ * <Reactive>{$ => {
498
+ * const rows = $.let(Data.bindPaged(jobs));
499
+ * const record = $.let(Record.bind(jobs, [jobsPatch]));
500
+ * return (
501
+ * <Sheet
502
+ * data={rows}
503
+ * columns={{
504
+ * task: Sheet.column.text(JobType, { header: "Task", width: "220px" }),
505
+ * qty: Sheet.column.integer(JobType, { header: "Qty", width: "96px" }),
506
+ * }}
507
+ * onApply={Record.onApply(record)}
508
+ * style={{ height: "360px" }}
509
+ * />
510
+ * );
511
+ * }}</Reactive>
512
+ * ));
513
+ * ```
514
+ */
515
+ declare function applyToRecord<K extends EastType, V extends EastType>(handle: KeyedRecordHandle<K, V>, options?: RecordApplyOptions & {
516
+ keyed?: false;
517
+ }): RecordApplyFunction<ReturnType<typeof Editing.Types.ChangeSet<V>>>;
518
+ /**
519
+ * The entries form for a collection that hands keyed batches — the Plan.
520
+ *
521
+ * @typeParam K - The record's key type
522
+ * @typeParam V - The record's entry type
523
+ * @param handle - The record, bound with its patch mutation
524
+ * @param options - `{ keyed: true }`, and the patch mutation's name when it has one
525
+ * @returns An async East function over `Editing.Types.ChangeSet(V, K)`
526
+ */
527
+ declare function applyToRecord<K extends EastType, V extends EastType>(handle: KeyedRecordHandle<K, V>, options: RecordApplyOptions & {
528
+ keyed: true;
529
+ }): RecordApplyFunction<ReturnType<typeof Editing.Types.ChangeSet<V, K>>>;
530
+ /**
531
+ * The form over a collection inside one entry.
532
+ *
533
+ * @typeParam K - The record's key type
534
+ * @typeParam V - The record's entry type
535
+ * @typeParam C - The collection's row type
536
+ * @param handle - The record, bound with its patch mutation
537
+ * @param options - The entry, how its rows are read and replaced, and their identity field
538
+ * @returns An async East function over `Editing.Types.ChangeSet(C)`
539
+ */
540
+ declare function applyToRecord<K extends EastType, V extends EastType, C extends StructType>(handle: KeyedRecordHandle<K, V>, options: RecordApplyInsideOptions<K, V, C>): RecordApplyFunction<ReturnType<typeof Editing.Types.ChangeSet<C>>>;
320
541
  /**
321
542
  * The Record namespace — reactive record (read + mutate + history) binding for
322
- * e3 UI tasks.
543
+ * e3 UI tasks, and the Apply that commits an editable collection to a record.
323
544
  *
324
545
  * @remarks
325
546
  * `Record.bind` binds an `e3.record` and its mutations to a handle: `read` /
326
547
  * `status` / `history` observe the record's current state (served from the
327
548
  * shared dataset cache), and `mutate.<name>` applies a typed mutation
328
- * fire-and-forget under compare-and-swap, refreshing `read()` on commit.
549
+ * fire-and-forget under compare-and-swap, refreshing `read()` on commit;
550
+ * `commit.<name>` is the same write awaited. `Record.onApply` commits a
551
+ * Sheet's, a Plan's or a SnapGrid's batches to the record, each Apply one commit
552
+ * through its patch door.
329
553
  *
330
554
  * Use inside `Reactive.Root` for reactive re-rendering as the record and the
331
555
  * mutation lifecycle change.
332
556
  */
333
557
  export declare const Record: {
334
558
  readonly bind: typeof bindRecord;
559
+ readonly onApply: typeof applyToRecord;
335
560
  };
336
561
  export {};
337
562
  //# sourceMappingURL=record.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../../src/bind/record.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../../src/bind/record.ts"],"names":[],"mappings":"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"}
@@ -15,10 +15,16 @@
15
15
  * latest-wins mutation lifecycle. A record's mutations serialize server-side
16
16
  * under compare-and-swap, so one shared channel per record is the honest model.
17
17
  *
18
+ * `commit.<name>(…)` is the same write awaited: an async closure that resolves
19
+ * to the server's terminal outcome. `Record.onApply` builds on it — an
20
+ * editable collection's `onApply` that commits each batch to the record
21
+ * through its patch door, as one commit.
22
+ *
18
23
  * @packageDocumentation
19
24
  */
20
- import { East, ArrayType, BooleanType, FunctionType, IntegerType, NullType, OptionType, StringType, StructType, VariantType, } from '@elaraai/east';
21
- import { DatasetStatusType, RecordCommitInfoType } from '@elaraai/e3-types';
25
+ import { East, ArrayType, AsyncFunctionType, BooleanType, DictType, Expr, FunctionType, IntegerType, NullType, OptionType, PatchType, StringType, StructType, VariantType, dictPatchOpsType, isTypeEqual, none, printType, some, variant, } from '@elaraai/east';
26
+ import { DatasetStatusType, MutationResultType, RecordCommitInfoType } from '@elaraai/e3-types';
27
+ import { Editing } from '@elaraai/east-ui';
22
28
  // ============================================================================
23
29
  // Status + error + descriptor types
24
30
  // ============================================================================
@@ -79,6 +85,29 @@ export const RecordBindingType = StructType({
79
85
  name: StringType,
80
86
  mutations: ArrayType(StringType),
81
87
  });
88
+ /**
89
+ * The outcome of an awaited write (`commit.<name>`): the server's terminal
90
+ * `MutationResult`, passed on as it came, and `transport` for a call that got
91
+ * no answer.
92
+ *
93
+ * @remarks
94
+ * Only `committed` wrote anything. `transport` is the one outcome that leaves
95
+ * it unknown whether the write committed — it may have reached the server
96
+ * before its answer was lost — which is what the write's request id is for: a
97
+ * retry with the same id resolves to that commit instead of writing twice.
98
+ *
99
+ * @property committed - The new commit, and the record state it wrote
100
+ * @property invalid - A lookup, arity or signature error; nothing ran
101
+ * @property failed - The mutation's program failed; see its stderr
102
+ * @property timed_out - The program ran out of time; nothing was written
103
+ * @property conflict - The compare-and-swap lost the race `attempts` times, or
104
+ * the write no longer matched the record — then `detail` names the key
105
+ * @property transport - The call got no answer, so whether it committed is unknown
106
+ */
107
+ export const RecordOutcomeType = VariantType({
108
+ ...MutationResultType.fields.outcome.cases,
109
+ transport: StructType({ message: StringType }),
110
+ });
82
111
  // ============================================================================
83
112
  // Bind handle — return type of `Record.bind`.
84
113
  // ============================================================================
@@ -86,7 +115,7 @@ export const RecordBindingType = StructType({
86
115
  * The struct returned by every {@link Record.bind} call. The read side
87
116
  * (`read` / `status` / `history`) is served reactively from the shared dataset
88
117
  * cache; the write side lives under `mutate` — one closure per mutation plus
89
- * the shared lifecycle accessors.
118
+ * the shared lifecycle accessors — and under `commit`, the same writes awaited.
90
119
  *
91
120
  * @remarks
92
121
  * The handle's shape IS the signature: the runtime recovers the state type
@@ -107,6 +136,14 @@ export const RecordBindingType = StructType({
107
136
  * @property mutate - The write surface: one fire-and-forget closure per
108
137
  * mutation (typed from its def), plus the shared `pending` / `status` /
109
138
  * `error` / `cancel`.
139
+ * @property commit - The writes awaited: one async closure per mutation,
140
+ * taking a request id before the mutation's own arguments and resolving to
141
+ * its {@link RecordOutcomeType}. The id makes a retry after an unanswered
142
+ * call resolve to the first call's commit rather than writing twice; `""`
143
+ * is a write without one. It runs on `mutate`'s channel, so `pending` /
144
+ * `status` / `error` show it too, and a conflict reads the record and its
145
+ * history again before the outcome settles — `read` and `history` then show
146
+ * what the write lost to.
110
147
  * @property start - Launch the workspace dataflow without mutating, so tasks
111
148
  * that consume this record recompute against its latest committed state.
112
149
  * The standalone "Run" affordance — a mutation refreshes the record's own
@@ -121,18 +158,21 @@ export const RecordBindHandleType = (stateType, mutations) => {
121
158
  error: FunctionType([], OptionType(RecordErrorType)),
122
159
  cancel: FunctionType([], NullType),
123
160
  };
161
+ const commitFields = {};
124
162
  for (const [name, argTypes] of Object.entries(mutations)) {
125
163
  if (name in mutateFields) {
126
164
  throw new Error(`Record.bind: mutation name "${name}" collides with a reserved mutate ` +
127
165
  `field (pending/status/error/cancel). Rename the mutation.`);
128
166
  }
129
167
  mutateFields[name] = FunctionType(argTypes, NullType);
168
+ commitFields[name] = AsyncFunctionType([StringType, ...argTypes], RecordOutcomeType);
130
169
  }
131
170
  return StructType({
132
171
  read: FunctionType([], stateType),
133
172
  status: FunctionType([], DatasetStatusType),
134
173
  history: FunctionType([], OptionType(ArrayType(RecordCommitInfoType))),
135
174
  mutate: StructType(mutateFields),
175
+ commit: StructType(commitFields),
136
176
  start: FunctionType([], NullType),
137
177
  binding: RecordBindingType,
138
178
  });
@@ -166,6 +206,7 @@ export const recordBindPlatformFn = East.genericPlatform("record_bind", ["H"], [
166
206
  // `record_mutate` is generic over the per-mutation `ArgsStruct` `A` (the N args
167
207
  // bundled into one struct, since platform fns are fixed-arity): the impl recovers
168
208
  // the per-arg types from `A`'s fields and the values from the passed struct.
209
+ // `record_commit` is its awaited sibling, which takes the write's request id too.
169
210
  const record_read = East.genericPlatform("record_read", ["T"], [StringType], "T", { optional: true });
170
211
  const record_status = East.platform("record_status", [StringType], DatasetStatusType, { optional: true });
171
212
  const record_history = East.platform("record_history", [StringType], OptionType(ArrayType(RecordCommitInfoType)), { optional: true });
@@ -175,6 +216,7 @@ const record_mutate_status = East.platform("record_mutate_status", [StringType],
175
216
  const record_mutate_error = East.platform("record_mutate_error", [StringType], OptionType(RecordErrorType), { optional: true });
176
217
  const record_mutate_cancel = East.platform("record_mutate_cancel", [StringType], NullType, { optional: true });
177
218
  const record_mutate = East.genericPlatform("record_mutate", ["A"], [StringType, StringType, "A"], NullType, { optional: true });
219
+ const record_commit = East.asyncGenericPlatform("record_commit", ["A"], [StringType, StringType, StringType, "A"], RecordOutcomeType, { optional: true });
178
220
  /**
179
221
  * Low-level platform primitives that back {@link Record.bind}'s handle methods.
180
222
  *
@@ -201,6 +243,8 @@ export const RecordBindPrimitives = {
201
243
  mutateCancel: record_mutate_cancel,
202
244
  /** `record_mutate([ArgsStruct], recordName, mutationName, argsStruct) -> Null`. */
203
245
  mutate: record_mutate,
246
+ /** `record_commit([ArgsStruct], recordName, mutationName, requestId, argsStruct) ~> RecordOutcome` — the awaited write. */
247
+ commit: record_commit,
204
248
  };
205
249
  // ============================================================================
206
250
  // User-facing factory.
@@ -267,20 +311,216 @@ function bindRecord(record, mutations) {
267
311
  const nameValue = East.value(record.name, StringType);
268
312
  return recordBindPlatformFn([handleType], nameValue);
269
313
  }
314
+ /**
315
+ * An awaited write's outcome as the editing session's answer. A commit is
316
+ * applied at the record state it wrote, which a pinned source installs; a
317
+ * conflict carries the issues naming what went stale; anything else refused
318
+ * the write. A call that got no answer may have committed, so it throws: the
319
+ * session keeps the request, and its retry sends the same request id, which
320
+ * resolves to that commit rather than writing twice.
321
+ */
322
+ const settleWrite = East.function([RecordOutcomeType, ArrayType(Editing.Types.Issue)], Editing.Types.ApplyResult, ($, outcome, conflicts) => {
323
+ const result = $.let(variant("conflict", conflicts), Editing.Types.ApplyResult);
324
+ $.match(outcome, {
325
+ committed: ($, done) => { $.assign(result, variant("applied", { revision: some(done.stateHash) })); },
326
+ invalid: ($, refused) => {
327
+ $.assign(result, variant("rejected", [{ entry: "", row: none, field: none, message: refused.message }]));
328
+ },
329
+ failed: ($, refused) => {
330
+ $.assign(result, variant("rejected", [{ entry: "", row: none, field: none, message: East.str `The write failed: ${refused.stderr}` }]));
331
+ },
332
+ timed_out: ($, refused) => {
333
+ $.assign(result, variant("rejected", [{ entry: "", row: none, field: none, message: East.str `The write ran out of time after ${refused.ms} ms and wrote nothing` }]));
334
+ },
335
+ transport: ($, lost) => {
336
+ $.error(East.str `The write got no answer, so it may have committed — retry to find out: ${lost.message}`);
337
+ },
338
+ });
339
+ return result;
340
+ });
341
+ function applyToRecord(handle, options = {}) {
342
+ const fields = Expr.type(handle).fields;
343
+ const recordType = fields["read"].output;
344
+ if (recordType.type !== "Dict") {
345
+ throw new Error(`Record.onApply: an Apply writes a record's entries by key, so the record must be a Dict — ` +
346
+ `this one holds ${printType(recordType)}`);
347
+ }
348
+ const mutation = options.mutation ?? "patch";
349
+ const commits = fields["commit"];
350
+ const door = commits?.type === "Struct" ? commits.fields[mutation] : undefined;
351
+ if (door === undefined || !isTypeEqual(door, AsyncFunctionType([StringType, PatchType(recordType)], RecordOutcomeType))) {
352
+ throw new Error(`Record.onApply: "${mutation}" is not bound as this record's patch door — bind the record with ` +
353
+ `e3.mutation.patch(record${mutation === "patch" ? "" : `, "${mutation}"`}) among its mutations`);
354
+ }
355
+ // The handle's fields as the Apply reads them; entries are opaque here, as
356
+ // in Editing.apply, and their runtime types are exact.
357
+ const bound = handle;
358
+ const write = bound.commit[mutation];
359
+ // Who changed the record last, for a conflict's words. A conflict reads the
360
+ // history again before the write settles, so it is current.
361
+ const lastChange = East.function([], StringType, (_$) => bound.history().match({
362
+ some: (_$2, commits) => commits.tryGet(0n).match({
363
+ some: (_$3, head) => East.str ` — last changed by ${head.actor}`,
364
+ none: (_$3) => East.str ``,
365
+ }),
366
+ none: (_$2) => East.str ``,
367
+ }));
368
+ if ("entry" in options) {
369
+ const entryType = recordType.value;
370
+ const get = East.value(options.get);
371
+ const read = Expr.type(get);
372
+ if (read.type !== "Function" || read.inputs.length !== 1 || !isTypeEqual(read.inputs[0], entryType)
373
+ || read.output.type !== "Array" || read.output.value.type !== "Struct") {
374
+ throw new Error("Record.onApply: `get` must be an East function from the record's entry to an Array of row structs");
375
+ }
376
+ const rowType = read.output.value;
377
+ const rowsType = ArrayType(rowType);
378
+ const set = East.value(options.set);
379
+ const replace = Expr.type(set);
380
+ if (replace.type !== "Function" || replace.inputs.length !== 2 || !isTypeEqual(replace.inputs[0], entryType)
381
+ || !isTypeEqual(replace.inputs[1], rowsType) || !isTypeEqual(replace.output, entryType)) {
382
+ throw new Error("Record.onApply: `set` must be an East function from the record's entry and its new rows to the entry");
383
+ }
384
+ const entryKey = East.value(options.entry, recordType.key);
385
+ const apply = Editing.apply(rowType, options.idField);
386
+ return East.asyncFunction([Editing.Types.ChangeSet(rowType)], Editing.Types.ApplyResult, ($, batch) => {
387
+ const rowsOf = $.const(get);
388
+ const withRows = $.const(set);
389
+ const applyRows = $.const(apply);
390
+ const commit = $.const(write);
391
+ const settle = $.const(settleWrite);
392
+ const changedBy = $.const(lastChange);
393
+ const key = $.const(entryKey);
394
+ const result = $.let(variant("conflict", []), Editing.Types.ApplyResult);
395
+ $.match(batch.base, {
396
+ revision: ($) => {
397
+ $.assign(result, variant("rejected", [{ entry: "", row: none, field: none,
398
+ message: "Rows inside a record entry are checked against the rows the edit began from — a snapshot, never a revision" }]));
399
+ },
400
+ snapshot: ($, rows) => {
401
+ $.match(bound.read().tryGet(key), {
402
+ none: ($) => {
403
+ $.assign(result, variant("conflict", [{ entry: "", row: none, field: none,
404
+ message: East.str `The entry was removed since this edit began${changedBy()}` }]));
405
+ },
406
+ some: ($, current) => {
407
+ $.match(applyRows(rows, batch, none), {
408
+ conflict: ($, issues) => { $.assign(result, variant("conflict", issues)); },
409
+ applied: ($, next) => {
410
+ const now = $.const(rowsOf(current));
411
+ // Moved since the edit began — unless to this very edit's
412
+ // rows, which a retry after an unanswered write finds.
413
+ $.if(East.notEqual(now, rows).and(() => East.notEqual(now, next)), ($) => {
414
+ $.assign(result, variant("conflict", [{ entry: "", row: none, field: none,
415
+ message: East.str `The entry changed since this edit began${changedBy()}` }]));
416
+ }).else(($) => {
417
+ // The entry before and after the edit, each alone in a record:
418
+ // their diff reaches the entry's rows and nothing else.
419
+ const before = $.let(new Map(), recordType);
420
+ $(before.insert(key, withRows(current, rows)));
421
+ const after = $.let(new Map(), recordType);
422
+ $(after.insert(key, withRows(current, next)));
423
+ const outcome = $.const(commit(batch.requestId, East.diff(before, after)));
424
+ const conflicts = $.let([], ArrayType(Editing.Types.Issue));
425
+ $.match(outcome, {
426
+ conflict: ($) => {
427
+ $(conflicts.pushLast({ entry: "", row: none, field: none,
428
+ message: East.str `The entry changed since this edit began${changedBy()}` }));
429
+ },
430
+ });
431
+ $.assign(result, settle(outcome, conflicts));
432
+ });
433
+ },
434
+ });
435
+ },
436
+ });
437
+ },
438
+ });
439
+ return result;
440
+ });
441
+ }
442
+ const keyType = recordType.key;
443
+ const entryType = recordType.value;
444
+ const batchType = "keyed" in options && options.keyed === true
445
+ ? Editing.Types.ChangeSet(entryType, keyType)
446
+ : Editing.Types.ChangeSet(entryType);
447
+ // A change names its entry by the key's text: a String key is its own
448
+ // text, any other key its `.east` printing, read back here.
449
+ const keyOf = keyType.type === "String"
450
+ ? East.function([StringType], StringType, (_$, id) => id)
451
+ : East.function([StringType], keyType, (_$, id) => id.parse(keyType));
452
+ return East.asyncFunction([batchType], Editing.Types.ApplyResult, ($, batch) => {
453
+ const keyFrom = $.const(keyOf);
454
+ const commit = $.const(write);
455
+ const settle = $.const(settleWrite);
456
+ const changedBy = $.const(lastChange);
457
+ // Each change is its entry's insert, update or delete, restated by key.
458
+ // The values and entry patches are the batch's own, carrying what each
459
+ // entry was when the edit began, which the record checks.
460
+ const ops = $.let(new Map(), DictType(keyType, dictPatchOpsType(entryType)));
461
+ $.for(batch.changes, ($, change) => {
462
+ const key = $.const(keyFrom(change.id));
463
+ $.match(change.patch, {
464
+ replace: ($, swap) => {
465
+ $.match(swap.after, {
466
+ some: ($, after) => {
467
+ $.match(swap.before, {
468
+ none: ($) => { $(ops.insert(key, variant("insert", after))); },
469
+ some: ($, before) => { $(ops.insert(key, variant("update", East.diff(before, after)))); },
470
+ });
471
+ },
472
+ none: ($) => {
473
+ $.match(swap.before, { some: ($, before) => { $(ops.insert(key, variant("delete", before))); } });
474
+ },
475
+ });
476
+ },
477
+ patch: ($, entry) => {
478
+ $.match(entry, { some: ($, update) => { $(ops.insert(key, variant("update", update))); } });
479
+ },
480
+ });
481
+ });
482
+ const outcome = $.const(commit(batch.requestId, variant("patch", ops)));
483
+ const conflicts = $.let([], ArrayType(Editing.Types.Issue));
484
+ $.match(outcome, {
485
+ conflict: ($) => {
486
+ // The entries whose change no longer applies to the record as it
487
+ // stands are the ones another write moved.
488
+ const current = $.const(bound.read());
489
+ const by = $.const(changedBy());
490
+ $.for(batch.changes, ($, change) => {
491
+ const key = $.const(keyFrom(change.id));
492
+ $.try(($) => {
493
+ $(East.applyPatch(current.tryGet(key), change.patch));
494
+ }).catch(($) => {
495
+ $(conflicts.pushLast({ entry: change.id, row: none, field: none, message: East.str `Changed since this edit began${by}` }));
496
+ });
497
+ });
498
+ $.if(conflicts.size().equal(0n), ($) => {
499
+ $(conflicts.pushLast({ entry: "", row: none, field: none, message: East.str `The record changed since this edit began${by}` }));
500
+ });
501
+ },
502
+ });
503
+ return settle(outcome, conflicts);
504
+ });
505
+ }
270
506
  /**
271
507
  * The Record namespace — reactive record (read + mutate + history) binding for
272
- * e3 UI tasks.
508
+ * e3 UI tasks, and the Apply that commits an editable collection to a record.
273
509
  *
274
510
  * @remarks
275
511
  * `Record.bind` binds an `e3.record` and its mutations to a handle: `read` /
276
512
  * `status` / `history` observe the record's current state (served from the
277
513
  * shared dataset cache), and `mutate.<name>` applies a typed mutation
278
- * fire-and-forget under compare-and-swap, refreshing `read()` on commit.
514
+ * fire-and-forget under compare-and-swap, refreshing `read()` on commit;
515
+ * `commit.<name>` is the same write awaited. `Record.onApply` commits a
516
+ * Sheet's, a Plan's or a SnapGrid's batches to the record, each Apply one commit
517
+ * through its patch door.
279
518
  *
280
519
  * Use inside `Reactive.Root` for reactive re-rendering as the record and the
281
520
  * mutation lifecycle change.
282
521
  */
283
522
  export const Record = {
284
523
  bind: bindRecord,
524
+ onApply: applyToRecord,
285
525
  };
286
526
  //# sourceMappingURL=record.js.map