@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.
- package/dist/src/bind/record.d.ts +229 -4
- package/dist/src/bind/record.d.ts.map +1 -1
- package/dist/src/bind/record.js +245 -5
- package/dist/src/bind/record.js.map +1 -1
- package/dist/src/index.d.ts +2 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/internal.d.ts +2 -1
- package/dist/src/internal.d.ts.map +1 -1
- package/dist/src/internal.js +5 -1
- package/dist/src/internal.js.map +1 -1
- package/dist/src/studio/builder.d.ts +3123 -0
- package/dist/src/studio/builder.d.ts.map +1 -0
- package/dist/src/studio/builder.js +115 -0
- package/dist/src/studio/builder.js.map +1 -0
- package/dist/src/studio/canvas.d.ts +83 -0
- package/dist/src/studio/canvas.d.ts.map +1 -0
- package/dist/src/studio/canvas.js +71 -0
- package/dist/src/studio/canvas.js.map +1 -0
- package/dist/src/studio/component.d.ts +178 -0
- package/dist/src/studio/component.d.ts.map +1 -0
- package/dist/src/studio/component.js +189 -0
- package/dist/src/studio/component.js.map +1 -0
- package/dist/src/studio/index.d.ts +104 -0
- package/dist/src/studio/index.d.ts.map +1 -0
- package/dist/src/studio/index.js +75 -0
- package/dist/src/studio/index.js.map +1 -0
- package/dist/src/studio/inspector.d.ts +208 -0
- package/dist/src/studio/inspector.d.ts.map +1 -0
- package/dist/src/studio/inspector.js +155 -0
- package/dist/src/studio/inspector.js.map +1 -0
- package/dist/src/studio/library.d.ts +3506 -0
- package/dist/src/studio/library.d.ts.map +1 -0
- package/dist/src/studio/library.js +245 -0
- package/dist/src/studio/library.js.map +1 -0
- package/dist/src/studio/page.d.ts +305 -0
- package/dist/src/studio/page.d.ts.map +1 -0
- package/dist/src/studio/page.js +105 -0
- package/dist/src/studio/page.js.map +1 -0
- package/dist/src/studio/pages.d.ts +10929 -0
- package/dist/src/studio/pages.d.ts.map +1 -0
- package/dist/src/studio/pages.js +502 -0
- package/dist/src/studio/pages.js.map +1 -0
- package/dist/src/studio/palette.d.ts +179 -0
- package/dist/src/studio/palette.d.ts.map +1 -0
- package/dist/src/studio/palette.js +120 -0
- package/dist/src/studio/palette.js.map +1 -0
- package/dist/src/studio/publish.d.ts +397 -0
- package/dist/src/studio/publish.d.ts.map +1 -0
- package/dist/src/studio/publish.js +198 -0
- package/dist/src/studio/publish.js.map +1 -0
- package/dist/test/bind/record/record.examples.d.ts +65 -1
- package/dist/test/bind/record/record.examples.d.ts.map +1 -1
- package/dist/test/bind/record/record.examples.js +21 -3
- package/dist/test/bind/record/record.examples.js.map +1 -1
- package/dist/test/studio/component.examples.d.ts +7 -0
- package/dist/test/studio/component.examples.d.ts.map +1 -0
- package/dist/test/studio/component.examples.js +46 -0
- package/dist/test/studio/component.examples.js.map +1 -0
- package/dist/test/studio/page.examples.d.ts +7 -0
- package/dist/test/studio/page.examples.d.ts.map +1 -0
- package/dist/test/studio/page.examples.js +64 -0
- package/dist/test/studio/page.examples.js.map +1 -0
- package/dist/test/studio/studio.examples.d.ts +1766 -0
- package/dist/test/studio/studio.examples.d.ts.map +1 -0
- package/dist/test/studio/studio.examples.js +354 -0
- package/dist/test/studio/studio.examples.js.map +1 -0
- 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":"AAAA;;;GAGG;AAEH
|
|
1
|
+
{"version":3,"file":"record.d.ts","sourceRoot":"","sources":["../../../src/bind/record.ts"],"names":[],"mappings":"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"}
|
package/dist/src/bind/record.js
CHANGED
|
@@ -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
|