@elaraai/e3-types 1.0.83 → 1.0.85

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 (92) hide show
  1. package/dist/src/api.d.ts +583 -17
  2. package/dist/src/api.d.ts.map +1 -1
  3. package/dist/src/api.js +208 -22
  4. package/dist/src/api.js.map +1 -1
  5. package/dist/src/dataflow.d.ts +115 -12
  6. package/dist/src/dataflow.d.ts.map +1 -1
  7. package/dist/src/dataflow.js +151 -15
  8. package/dist/src/dataflow.js.map +1 -1
  9. package/dist/src/dataflow.spec.js +41 -7
  10. package/dist/src/dataflow.spec.js.map +1 -1
  11. package/dist/src/dataset-blob.d.ts +19 -2
  12. package/dist/src/dataset-blob.d.ts.map +1 -1
  13. package/dist/src/dataset-blob.js +100 -21
  14. package/dist/src/dataset-blob.js.map +1 -1
  15. package/dist/src/dataset-blob.spec.js +132 -0
  16. package/dist/src/dataset-blob.spec.js.map +1 -1
  17. package/dist/src/execution.d.ts +68 -8
  18. package/dist/src/execution.d.ts.map +1 -1
  19. package/dist/src/execution.js +49 -8
  20. package/dist/src/execution.js.map +1 -1
  21. package/dist/src/execution.spec.js +31 -9
  22. package/dist/src/execution.spec.js.map +1 -1
  23. package/dist/src/function.d.ts +12 -0
  24. package/dist/src/function.d.ts.map +1 -1
  25. package/dist/src/index.d.ts +9 -8
  26. package/dist/src/index.d.ts.map +1 -1
  27. package/dist/src/index.js +15 -12
  28. package/dist/src/index.js.map +1 -1
  29. package/dist/src/names.d.ts +9 -0
  30. package/dist/src/names.d.ts.map +1 -1
  31. package/dist/src/names.js.map +1 -1
  32. package/dist/src/pieces.d.ts +47 -0
  33. package/dist/src/pieces.d.ts.map +1 -0
  34. package/dist/src/pieces.js +17 -0
  35. package/dist/src/pieces.js.map +1 -0
  36. package/dist/src/pieces.spec.d.ts +6 -0
  37. package/dist/src/pieces.spec.d.ts.map +1 -0
  38. package/dist/src/pieces.spec.js +20 -0
  39. package/dist/src/pieces.spec.js.map +1 -0
  40. package/dist/src/record.d.ts +52 -0
  41. package/dist/src/record.d.ts.map +1 -1
  42. package/dist/src/record.js +25 -0
  43. package/dist/src/record.js.map +1 -1
  44. package/dist/src/record.spec.js +43 -6
  45. package/dist/src/record.spec.js.map +1 -1
  46. package/dist/src/release.d.ts +1 -1
  47. package/dist/src/release.js +1 -1
  48. package/dist/src/repository.d.ts +16 -0
  49. package/dist/src/repository.d.ts.map +1 -1
  50. package/dist/src/repository.js +15 -0
  51. package/dist/src/repository.js.map +1 -1
  52. package/dist/src/runner.d.ts +22 -7
  53. package/dist/src/runner.d.ts.map +1 -1
  54. package/dist/src/runner.js +14 -11
  55. package/dist/src/runner.js.map +1 -1
  56. package/dist/src/runner.spec.js +4 -4
  57. package/dist/src/runner.spec.js.map +1 -1
  58. package/dist/src/structure.d.ts +27 -0
  59. package/dist/src/structure.d.ts.map +1 -1
  60. package/dist/src/structure.js +59 -18
  61. package/dist/src/structure.js.map +1 -1
  62. package/dist/src/structure.spec.d.ts +6 -0
  63. package/dist/src/structure.spec.d.ts.map +1 -0
  64. package/dist/src/structure.spec.js +57 -0
  65. package/dist/src/structure.spec.js.map +1 -0
  66. package/dist/src/task.d.ts +13 -1
  67. package/dist/src/task.d.ts.map +1 -1
  68. package/dist/src/task.js +1 -1
  69. package/dist/src/task.spec.js +25 -4
  70. package/dist/src/task.spec.js.map +1 -1
  71. package/package.json +3 -2
  72. package/src/api.ts +225 -21
  73. package/src/dataflow.spec.ts +49 -7
  74. package/src/dataflow.ts +160 -14
  75. package/src/dataset-blob.spec.ts +147 -0
  76. package/src/dataset-blob.ts +97 -20
  77. package/src/execution.spec.ts +31 -9
  78. package/src/execution.ts +52 -7
  79. package/src/index.ts +39 -2
  80. package/src/names.ts +12 -0
  81. package/src/pieces.spec.ts +23 -0
  82. package/src/pieces.ts +49 -0
  83. package/src/record.spec.ts +51 -5
  84. package/src/record.ts +28 -0
  85. package/src/release.ts +1 -1
  86. package/src/repository.ts +18 -0
  87. package/src/runner.spec.ts +4 -4
  88. package/src/runner.ts +14 -11
  89. package/src/structure.spec.ts +65 -0
  90. package/src/structure.ts +64 -21
  91. package/src/task.spec.ts +29 -3
  92. package/src/task.ts +1 -1
@@ -5,15 +5,16 @@
5
5
 
6
6
  /**
7
7
  * Execution status wire: the typed outcomes with their peaks, a split task's
8
- * plan and whether the execution is a unit, what each keeps from gc, and the
9
- * refusal of a record an older e3 wrote before `cancelled` and `interrupted`
10
- * were cases of it, before a runner's peak was recorded, before a split task's
11
- * plan was, or before a record said whether it is a unit.
8
+ * plan, whether the execution is a unit and why a stopped one stopped, what
9
+ * each keeps from gc, and the refusal of a record an older e3 wrote before
10
+ * `cancelled` and `interrupted` were cases of it, before a runner's peak was
11
+ * recorded, before a split task's plan was, before a record said whether it
12
+ * is a unit, or before a stopped one said why.
12
13
  */
13
14
 
14
15
  import { describe, it } from 'node:test';
15
16
  import assert from 'node:assert/strict';
16
- import { ArrayType, DateTimeType, IntegerType, OptionType, StringType, StructType, VariantType, encodeBeast2For, none, some, variant } from '@elaraai/east';
17
+ import { ArrayType, BooleanType, DateTimeType, IntegerType, OptionType, StringType, StructType, VariantType, encodeBeast2For, none, some, variant } from '@elaraai/east';
17
18
  import { ExecutionStatusType, decodeExecutionStatus, executionStatusRoots, type ExecutionStatus } from './execution.js';
18
19
 
19
20
  /** The four-case wire every record had before the typed outcomes. */
@@ -45,7 +46,7 @@ describe('ExecutionStatusType', () => {
45
46
  };
46
47
  const encodeOlder = encodeBeast2For(PreOutcomeExecutionStatusType);
47
48
 
48
- it('round-trips the typed outcomes, with a runner\'s peak or without one, a task\'s own or a unit\'s', () => {
49
+ it('round-trips the typed outcomes, with a runner\'s peak or without one, a task\'s own or a unit\'s, and why a stopped one stopped', () => {
49
50
  const own = { ...stopped, unit: false };
50
51
  const statuses: ExecutionStatus[] = [
51
52
  variant('success', { ...own, outputHash: 'b'.repeat(64), peakBytes: some(96n * 1024n ** 2n), plan: none }),
@@ -54,8 +55,11 @@ describe('ExecutionStatusType', () => {
54
55
  variant('running', { executionId: '0199-b', inputHashes: ['a'.repeat(64)], startedAt: new Date(1000), pid: 4242n, pidStartTime: 7n, bootId: 'boot', unit: true }),
55
56
  variant('failed', { ...own, exitCode: 1n, peakBytes: some(412n * 1024n ** 2n) }),
56
57
  variant('failed', { ...own, exitCode: -1n, peakBytes: none }),
57
- variant('cancelled', own),
58
- variant('interrupted', { ...own, pid: 4242n }),
58
+ variant('cancelled', { ...own, reason: { kind: variant('aborted', null), message: 'cancelled: e3 stopped the runner because the run was aborted' } }),
59
+ variant('interrupted', { ...own, pid: 4242n, reason: { kind: variant('owner_gone', null), message: 'its runner and its owner are gone' } }),
60
+ variant('interrupted', { ...own, pid: 0n, unit: true, reason: { kind: variant('yielded', null), message: 'its run yielded' } }),
61
+ variant('interrupted', { ...own, pid: 0n, reason: { kind: variant('host', 'OutOfMemoryError'), message: 'its container ran out of memory' } }),
62
+ variant('interrupted', { ...own, pid: 0n, reason: { kind: variant('unrecorded', null), message: '' } }),
59
63
  variant('error', { ...own, message: 'Failed to read output: no such file' }),
60
64
  ];
61
65
  const encode = encodeBeast2For(ExecutionStatusType);
@@ -67,7 +71,7 @@ describe('ExecutionStatusType', () => {
67
71
  assert.deepEqual(executionStatusRoots(variant('success', { ...success, plan: none })), ['b'.repeat(64)]);
68
72
  assert.deepEqual(executionStatusRoots(variant('success', { ...success, plan: some('c'.repeat(64)) })), ['b'.repeat(64), 'c'.repeat(64)]);
69
73
  assert.deepEqual(executionStatusRoots(variant('failed', { ...stopped, exitCode: 1n, peakBytes: none, unit: false })), []);
70
- assert.deepEqual(executionStatusRoots(variant('cancelled', { ...stopped, unit: false })), []);
74
+ assert.deepEqual(executionStatusRoots(variant('cancelled', { ...stopped, unit: false, reason: { kind: variant('aborted', null), message: '' } })), []);
71
75
  });
72
76
 
73
77
  it('keeps from gc the inputs a running attempt reads, passing over a merge\'s tag', () => {
@@ -113,6 +117,24 @@ describe('ExecutionStatusType', () => {
113
117
  }),
114
118
  });
115
119
  assert.throws(() => decodeExecutionStatus(encodeBeast2For(withoutUnits)(variant('success', { ...stopped, outputHash: 'b'.repeat(64), peakBytes: none, plan: none }))), refusal);
120
+ // With units, before a stopped record said why it stopped: only the
121
+ // repository upgrade that carries such a record forward reads it, and every
122
+ // case of it is refused, since its type names the cases that changed.
123
+ const withoutReasons = VariantType({
124
+ ...ExecutionStatusType.cases,
125
+ cancelled: StructType({ executionId: StringType, inputHashes: ArrayType(StringType), startedAt: DateTimeType, completedAt: DateTimeType, unit: BooleanType }),
126
+ interrupted: StructType({
127
+ executionId: StringType, inputHashes: ArrayType(StringType), startedAt: DateTimeType, completedAt: DateTimeType, pid: IntegerType, unit: BooleanType,
128
+ }),
129
+ });
130
+ const own = { ...stopped, unit: false };
131
+ for (const record of [
132
+ variant('cancelled', own),
133
+ variant('interrupted', { ...own, pid: 4242n }),
134
+ variant('success', { ...own, outputHash: 'b'.repeat(64), peakBytes: none, plan: none }),
135
+ ] as const) {
136
+ assert.throws(() => decodeExecutionStatus(encodeBeast2For(withoutReasons)(record)), refusal);
137
+ }
116
138
  });
117
139
 
118
140
  it('throws for bytes of no status shape', () => {
package/src/execution.ts CHANGED
@@ -15,7 +15,7 @@
15
15
  * - For success: output hash, timing, peak memory and a split task's last plan
16
16
  * - For failed: exit code, timing and peak memory
17
17
  * - For error: internal error message and timing
18
- * - For cancelled and interrupted: how e3, not the task, ended it
18
+ * - For cancelled and interrupted: how e3, not the task, ended it, and why
19
19
  *
20
20
  * Every case says whether the execution is a unit of a split task — a piece,
21
21
  * or a merge of the pieces' outputs — which is recorded under its task's hash
@@ -31,10 +31,42 @@ import {
31
31
  IntegerType,
32
32
  BooleanType,
33
33
  DateTimeType,
34
+ NullType,
34
35
  ValueTypeOf,
35
36
  decodeBeast2For,
36
37
  } from '@elaraai/east';
37
38
 
39
+ /**
40
+ * Why an execution stopped without finishing: what a `cancelled` or an
41
+ * `interrupted` record says, written in the same write as the record.
42
+ *
43
+ * - `aborted`: the signal it ran under was aborted — its run was cancelled,
44
+ * or the call it ran for ended
45
+ * - `yielded`: its run yielded mid-stage, and a run resumed takes the stage up
46
+ * again
47
+ * - `owner_gone`: its runner and the process, or the browser tab, that owned
48
+ * it are gone
49
+ * - `host`: a host's own reason, by its code — e3-cloud's for a container
50
+ * that stopped, say
51
+ * - `unrecorded`: it stopped before e3 recorded reasons; only the repository
52
+ * upgrade that carried its record forward writes it
53
+ *
54
+ * `message` is what e3 wrote as the attempt's log's last line, after its
55
+ * `e3: `, or the host's words; empty for `unrecorded`.
56
+ */
57
+ export const StopReasonType = StructType({
58
+ kind: VariantType({
59
+ aborted: NullType,
60
+ yielded: NullType,
61
+ owner_gone: NullType,
62
+ host: StringType,
63
+ unrecorded: NullType,
64
+ }),
65
+ message: StringType,
66
+ });
67
+
68
+ export type StopReason = ValueTypeOf<typeof StopReasonType>;
69
+
38
70
  /** A running execution's process identification. */
39
71
  const RunningStatusType = StructType({
40
72
  /** Unique execution ID (UUIDv7) */
@@ -126,10 +158,14 @@ const ErrorStatusType = StructType({
126
158
  * - `success`: Task ran and returned exit code 0
127
159
  * - `failed`: Task ran and returned non-zero exit code
128
160
  * - `error`: e3 execution engine had an internal error (runner not found, output missing, etc.)
129
- * - `cancelled`: e3 stopped the execution because the run was aborted, before
130
- * its runner started or while it ran — not the task's own failure
131
- * - `interrupted`: the orchestrator that owned the execution exited before it
132
- * finished, and its runner is gone too, so nothing will write its outcome
161
+ * - `cancelled`: e3 stopped the execution because the signal it ran under was
162
+ * aborted, before its runner started or while it ran — not the task's own
163
+ * failure
164
+ * - `interrupted`: the execution can no longer finish, and nothing will write
165
+ * its outcome: its runner and its owner are gone, its host says it cannot,
166
+ * or its run yielded mid-stage
167
+ *
168
+ * Each of the two says why it stopped ({@link StopReasonType}).
133
169
  *
134
170
  * The `running` state includes process identification fields (pid, pidStartTime, bootId)
135
171
  * to enable detection of crashed executions. See design/e3-execution.md for details.
@@ -154,6 +190,8 @@ export const ExecutionStatusType = VariantType({
154
190
  * the pieces' outputs — rather than a task's own execution, which the one
155
191
  * unit of a task whose input closes no piece is */
156
192
  unit: BooleanType,
193
+ /** Why it stopped: `aborted` */
194
+ reason: StopReasonType,
157
195
  }),
158
196
  interrupted: StructType({
159
197
  /** Unique execution ID (UUIDv7) */
@@ -170,6 +208,9 @@ export const ExecutionStatusType = VariantType({
170
208
  * the pieces' outputs — rather than a task's own execution, which the one
171
209
  * unit of a task whose input closes no piece is */
172
210
  unit: BooleanType,
211
+ /** Why it can no longer finish: `owner_gone`, `yielded`, or its host's
212
+ * reason */
213
+ reason: StopReasonType,
173
214
  }),
174
215
  });
175
216
 
@@ -209,8 +250,12 @@ const decodeCurrentStatus = decodeBeast2For(ExecutionStatusType);
209
250
  * Decode an execution status.
210
251
  *
211
252
  * @remarks
212
- * Stored state, so it changes by hard cutover: a repository an older e3 wrote
213
- * is re-created rather than read, and this says so.
253
+ * Stored state, which changes as `docs/conventions/WIRE_MIGRATION.md` says:
254
+ * this reads the current form alone. A repository's upgrade steps carry its
255
+ * records into it when an e3 first opens the repository — the stop reasons'
256
+ * step those from before a stopped execution recorded why — so a record in an
257
+ * earlier form here is one from before repositories recorded their upgrades,
258
+ * whose repository is re-created, and this says so.
214
259
  *
215
260
  * @param data - the stored bytes
216
261
  * @returns the status
package/src/index.ts CHANGED
@@ -92,6 +92,7 @@ export {
92
92
  treePath,
93
93
  pathToString,
94
94
  parsePath,
95
+ parseKeypath,
95
96
  parseDatasetPath,
96
97
  parsePackageRef,
97
98
  urlPathToTreePath,
@@ -122,6 +123,7 @@ export {
122
123
  decodeRecordObject,
123
124
  RecordIndexObjectType,
124
125
  type RecordIndexObject,
126
+ decodeRecordIndexObject,
125
127
  RECORD_STATE_KIND,
126
128
  RecordStateType,
127
129
  type RecordState,
@@ -184,6 +186,8 @@ export {
184
186
 
185
187
  // Execution status
186
188
  export {
189
+ StopReasonType,
190
+ type StopReason,
187
191
  ExecutionStatusType,
188
192
  type ExecutionStatus,
189
193
  decodeExecutionStatus,
@@ -223,6 +227,8 @@ export {
223
227
  export {
224
228
  RepositoryRecordType,
225
229
  type RepositoryRecord,
230
+ RepositoryUpgradeProgressType,
231
+ type RepositoryUpgradeProgress,
226
232
  RepoStatusType,
227
233
  type RepoStatus,
228
234
  RepoMetadataType,
@@ -232,8 +238,8 @@ export {
232
238
  // The release of e3 this build is, which what it keeps and ships records
233
239
  export { E3_RELEASE, compareReleases } from './release.js';
234
240
 
235
- // The names e3 makes paths of
236
- export { type NamedKind, nameProblem } from './names.js';
241
+ // The names e3 makes paths of, and the hashes and ids
242
+ export { type NamedKind, type HashKind, type IdKind, type IdentifierKind, nameProblem } from './names.js';
237
243
 
238
244
  // Dataset transfer types
239
245
  export {
@@ -256,6 +262,7 @@ export {
256
262
  isCollectionRoot,
257
263
  encodeDatasetBlob,
258
264
  writeCollectionManifest,
265
+ SEGMENT_WRITES_IN_FLIGHT,
259
266
  type CollectionSegmentRef,
260
267
  type CollectionPiece,
261
268
  type SegmentSink,
@@ -355,6 +362,7 @@ export {
355
362
  // Execution
356
363
  DataflowRequestType,
357
364
  LogChunkType,
365
+ TaskLogChunkType,
358
366
  TaskExecutionResultType,
359
367
  DataflowResultType,
360
368
  // Dataflow API polling
@@ -365,9 +373,12 @@ export {
365
373
  UnitWaitType,
366
374
  SplitProgressType,
367
375
  ApiDataflowExecutionStateType,
376
+ DATAFLOW_POLL_EVENTS_MAX,
368
377
  // Task Execution History
369
378
  ExecutionHistoryStatusType,
370
379
  ExecutionListItemType,
380
+ TASK_EXECUTIONS_PAGE_DEFAULT,
381
+ TASK_EXECUTIONS_PAGE_MAX,
371
382
  // Dataset List
372
383
  TreeKindType,
373
384
  ListEntryType,
@@ -377,6 +388,7 @@ export {
377
388
  ExecuteLimitsType,
378
389
  DiagnosticType,
379
390
  ExecuteResultType,
391
+ CallArgType,
380
392
  FunctionCallRequestType,
381
393
  FunctionSignatureType,
382
394
  OneShotRequestType,
@@ -388,9 +400,16 @@ export {
388
400
  SplitCallPlanType,
389
401
  MutationCallRequestType,
390
402
  MutationResultType,
403
+ RecordMigrationAppliedType,
391
404
  RecordSignatureType,
392
405
  RecordCommitInfoType,
406
+ RecordArgPreviewType,
407
+ RecordCommitArgsType,
408
+ RecordHistoryCommitType,
393
409
  RecordHistoryResultType,
410
+ RECORD_ARG_TEXT_CHARS,
411
+ RECORD_ARG_TEXT_BYTES,
412
+ RECORD_ARGS_READ_BYTES,
394
413
  // Type aliases
395
414
  type Error,
396
415
  type RepositoryStatus,
@@ -424,6 +443,7 @@ export {
424
443
  type TaskDetails,
425
444
  type DataflowRequest,
426
445
  type LogChunk,
446
+ type TaskLogChunk,
427
447
  type TaskExecutionResult,
428
448
  type DataflowResult,
429
449
  type DataflowEvent,
@@ -441,6 +461,7 @@ export {
441
461
  type ExecuteLimits,
442
462
  type Diagnostic,
443
463
  type ExecuteResult,
464
+ type CallArg,
444
465
  type FunctionCallRequest,
445
466
  type FunctionSignature,
446
467
  type OneShotRequest,
@@ -451,8 +472,12 @@ export {
451
472
  type SplitCallPlan,
452
473
  type MutationCallRequest,
453
474
  type MutationResult,
475
+ type RecordMigrationApplied,
454
476
  type RecordSignature,
455
477
  type RecordCommitInfo,
478
+ type RecordArgPreview,
479
+ type RecordCommitArgs,
480
+ type RecordHistoryCommit,
456
481
  type RecordHistoryResult,
457
482
  } from './api.js';
458
483
 
@@ -473,9 +498,17 @@ export {
473
498
  ExecutionEventType,
474
499
  type ExecutionEvent,
475
500
  type PartitionProgress,
501
+ DataflowForceType,
502
+ type DataflowForce,
503
+ dataflowForce,
504
+ dataflowForceOption,
476
505
  DataflowExecutionStateType,
477
506
  type DataflowExecutionState,
478
507
  decodeDataflowExecutionState,
508
+ ExecutionStateSummaryType,
509
+ type ExecutionStateSummary,
510
+ executionStateSummary,
511
+ lastEventSeq,
479
512
  // Dataflow run history
480
513
  DataflowRunStatusType,
481
514
  type DataflowRunStatus,
@@ -487,6 +520,10 @@ export {
487
520
  type DataflowRun,
488
521
  } from './dataflow.js';
489
522
 
523
+ // The sizes e3 cuts a split task's input into pieces at, which a caller
524
+ // planning a split call weighs a dataset against
525
+ export { PIECE_SIZES, type PieceSizes } from './pieces.js';
526
+
490
527
  // A split task's unit graph, a stage at a time
491
528
  export {
492
529
  UNIT_PLAN_KIND,
package/src/names.ts CHANGED
@@ -13,6 +13,18 @@
13
13
  /** What a name names. */
14
14
  export type NamedKind = 'repository' | 'workspace' | 'package' | 'package version' | 'lock';
15
15
 
16
+ /** What a hash e3 makes a path or a key of names: an object, or an
17
+ * execution's task or inputs. */
18
+ export type HashKind = 'object hash' | 'task hash' | 'inputs hash';
19
+
20
+ /** What an id e3 makes a path or a key of names: an execution attempt, a
21
+ * dataflow run, or a gc run in steps. */
22
+ export type IdKind = 'execution id' | 'run id' | 'gc run id';
23
+
24
+ /** What a hash or an id e3 makes a path or a key of names: not a name, so
25
+ * checked for its form rather than by {@link nameProblem}. */
26
+ export type IdentifierKind = HashKind | IdKind;
27
+
16
28
  /** What no name may hold: a path separator, a character a Windows file name
17
29
  * refuses, or a control character. */
18
30
  const REFUSED = /[/\\:*?"<>|\u0000-\u001f]/;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+
6
+ /**
7
+ * The platform's piece sizes, which e3-core's piece rule closes pieces at and
8
+ * a caller planning a split call weighs a dataset against.
9
+ */
10
+
11
+ import { describe, it } from 'node:test';
12
+ import assert from 'node:assert/strict';
13
+ import { PIECE_SIZES } from './index.js';
14
+
15
+ describe('PIECE_SIZES', () => {
16
+ it('is 16, 64 and 256 MiB, exported from the package', () => {
17
+ assert.deepEqual(PIECE_SIZES, { min: 16 * 2 ** 20, target: 64 * 2 ** 20, max: 256 * 2 ** 20 });
18
+ });
19
+
20
+ it('orders its sizes: the least a piece holds, its middle, and the most', () => {
21
+ assert.ok(PIECE_SIZES.min < PIECE_SIZES.target && PIECE_SIZES.target < PIECE_SIZES.max);
22
+ });
23
+ });
package/src/pieces.ts ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Copyright (c) 2025 Elara AI Pty Ltd
3
+ * Dual-licensed under AGPL-3.0 and commercial license. See LICENSE for details.
4
+ */
5
+
6
+ /**
7
+ * The sizes e3 cuts a split task's input into pieces at.
8
+ *
9
+ * They live here, beside the wire types, so a caller that plans a split call
10
+ * in a browser — the query builder — weighs a dataset against the smallest
11
+ * piece e3 cuts without importing e3-core. e3-core's piece rule closes pieces
12
+ * at these sizes, and its `pieceSizes()` gives the sizes a process plans with:
13
+ * these, or the sizes a test sets.
14
+ *
15
+ * @packageDocumentation
16
+ */
17
+
18
+ /**
19
+ * The sizes the piece rule closes pieces at, in stored bytes: a piece closes
20
+ * only once it holds `min`, weighs its segments against `max` until it holds
21
+ * `target` and against `min` after, and always closes once it holds `max`.
22
+ *
23
+ * @remarks
24
+ * A dataset that weighs no more than `min` is one piece, which is why a
25
+ * caller compares a dataset's stored bytes with `min` to tell whether a split
26
+ * call would split it.
27
+ */
28
+ export interface PieceSizes {
29
+ /** The least a piece holds, unless the input ends first. */
30
+ readonly min: number;
31
+ /** Where a piece starts closing readily: the pieces' middle size, which a
32
+ * merge range aims for too. */
33
+ readonly target: number;
34
+ /** The most a piece holds, but for the rest of a `by` group. */
35
+ readonly max: number;
36
+ }
37
+
38
+ /**
39
+ * The platform's piece sizes: 16, 64 and 256 MiB.
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * import { PIECE_SIZES } from '@elaraai/e3-types';
44
+ *
45
+ * // A dataset within one piece is read in one call.
46
+ * const split = storedBytes > PIECE_SIZES.min;
47
+ * ```
48
+ */
49
+ export const PIECE_SIZES: PieceSizes = { min: 16 * 2 ** 20, target: 64 * 2 ** 20, max: 256 * 2 ** 20 };
@@ -6,26 +6,29 @@
6
6
  /**
7
7
  * The record wire types' decoders: each reads the current shape, and refuses
8
8
  * an older one naming the fix — a commit an older e3 wrote, whose repository
9
- * is re-created, and a mutation, migration or record object an older SDK
10
- * exported, whose package is re-exported.
9
+ * is re-created, and a mutation, migration, index or record object an older
10
+ * SDK exported, whose package is re-exported.
11
11
  */
12
12
 
13
13
  import { describe, it } from 'node:test';
14
14
  import assert from 'node:assert/strict';
15
- import { DateTimeType, DictType, IntegerType, OptionType, StringType, StructType, ArrayType, EastTypeType, encodeBeast2For, isTypeValueEqual, some, toEastTypeValue, variant } from '@elaraai/east';
15
+ import { BooleanType, DateTimeType, DictType, IntegerType, OptionType, StringType, StructType, ArrayType, EastTypeType, VariantType, encodeBeast2For, isTypeValueEqual, some, toEastTypeValue, variant } from '@elaraai/east';
16
16
  import { RunnerType } from './runner.js';
17
17
  import {
18
18
  MigrationObjectType,
19
19
  MutationObjectType,
20
20
  RecordCommitType,
21
+ RecordIndexObjectType,
21
22
  RecordObjectType,
22
23
  decodeMigrationObject,
23
24
  decodeMutationObject,
24
25
  decodeRecordCommit,
26
+ decodeRecordIndexObject,
25
27
  decodeRecordObject,
26
28
  type MigrationObject,
27
29
  type MutationObject,
28
30
  type RecordCommit,
31
+ type RecordIndexObject,
29
32
  type RecordObject,
30
33
  } from './record.js';
31
34
 
@@ -65,7 +68,7 @@ describe('decodeMutationObject', () => {
65
68
  const mutation: MutationObject = {
66
69
  bodyIr: 'a'.repeat(64),
67
70
  argTypes: [toEastTypeValue(IntegerType)],
68
- runner: variant('east_node', { platforms: ['@elaraai/east-node-std'] }),
71
+ runner: variant('east_node', { platforms: ['@elaraai/east-node-std'], decode: variant('lazy', null) }),
69
72
  form: variant('edit', null),
70
73
  programIr: 'b'.repeat(64),
71
74
  };
@@ -113,6 +116,49 @@ describe('decodeMutationObject', () => {
113
116
  });
114
117
  });
115
118
 
119
+ describe('decodeRecordIndexObject', () => {
120
+ const index: RecordIndexObject = {
121
+ keyIr: 'a'.repeat(64),
122
+ multi: false,
123
+ valueIr: some('b'.repeat(64)),
124
+ keyType: toEastTypeValue(StringType),
125
+ valueType: toEastTypeValue(IntegerType),
126
+ buildIr: 'c'.repeat(64),
127
+ runner: variant('east_node', { platforms: ['@elaraai/east-node-std'], decode: variant('whole', null) }),
128
+ };
129
+
130
+ it('round-trips a current index object, its types among it', () => {
131
+ const decoded = decodeRecordIndexObject(encodeBeast2For(RecordIndexObjectType)(index));
132
+ // A decoded type value carries no memoized identity, so only the East
133
+ // comparison says whether two of them are the same type.
134
+ assert.ok(isTypeValueEqual(decoded.keyType, index.keyType));
135
+ assert.ok(isTypeValueEqual(decoded.valueType, index.valueType));
136
+ const { keyType: _keyType, valueType: _valueType, ...rest } = decoded;
137
+ const { keyType: _k, valueType: _v, ...expected } = index;
138
+ assert.deepEqual(rest, expected);
139
+ });
140
+
141
+ it('refuses an index object whose runner predates its decode, naming the fix', () => {
142
+ // A stock runner as a package exported before runners said how they read
143
+ // a program's inputs: its platforms alone.
144
+ const PreDecodeRunnerType = VariantType({
145
+ east_node: StructType({ platforms: ArrayType(StringType) }),
146
+ east_py: StructType({ platforms: ArrayType(StringType) }),
147
+ east_c: StructType({ platforms: ArrayType(StringType) }),
148
+ custom: StructType({ command: ArrayType(StringType) }),
149
+ });
150
+ const PreDecodeIndexType = StructType({
151
+ keyIr: StringType, multi: BooleanType, valueIr: OptionType(StringType), keyType: EastTypeType,
152
+ valueType: EastTypeType, buildIr: StringType, runner: PreDecodeRunnerType,
153
+ });
154
+ const older = encodeBeast2For(PreDecodeIndexType)({ ...index, runner: variant('east_node', { platforms: ['@elaraai/east-node-std'] }) });
155
+ assert.throws(
156
+ () => decodeRecordIndexObject(older),
157
+ /^Error: the index object does not decode: the package was exported by an older e3 SDK — re-export it with the current one \(/,
158
+ );
159
+ });
160
+ });
161
+
116
162
  describe('decodeMigrationObject', () => {
117
163
  const RowV1Type = StructType({ name: StringType });
118
164
  const RowV2Type = StructType({ name: StringType, shift: IntegerType });
@@ -122,7 +168,7 @@ describe('decodeMigrationObject', () => {
122
168
  to: toEastTypeValue(DictType(StringType, RowV2Type)),
123
169
  bodyIr: 'a'.repeat(64),
124
170
  programIr: 'b'.repeat(64),
125
- runner: variant('east_node', { platforms: ['@elaraai/east-node-std'] }),
171
+ runner: variant('east_node', { platforms: ['@elaraai/east-node-std'], decode: variant('lazy', null) }),
126
172
  };
127
173
 
128
174
  it('round-trips a current migration, its types among it', () => {
package/src/record.ts CHANGED
@@ -200,6 +200,34 @@ export const RecordIndexObjectType = StructType({
200
200
  export type RecordIndexObjectType = typeof RecordIndexObjectType;
201
201
  export type RecordIndexObject = ValueTypeOf<typeof RecordIndexObjectType>;
202
202
 
203
+ const decodeCurrentRecordIndex = decodeBeast2For(RecordIndexObjectType);
204
+
205
+ /**
206
+ * Decode a `RecordIndexObject`.
207
+ *
208
+ * @remarks
209
+ * A package-borne wire, so it changes by hard cutover: a package exported by
210
+ * an older SDK is re-exported with the current one, and this says so. A
211
+ * record's state names the index object each index was built under, so one an
212
+ * older SDK exported is read until a deploy of the re-exported package
213
+ * rebuilds the index under the current one.
214
+ *
215
+ * @param data - the stored bytes
216
+ * @returns the index object
217
+ * @throws {Error} When the bytes are not a current index object — a package
218
+ * exported by an older SDK, which is re-exported with the current one.
219
+ */
220
+ export function decodeRecordIndexObject(data: Uint8Array): RecordIndexObject {
221
+ try {
222
+ return decodeCurrentRecordIndex(data);
223
+ } catch (err) {
224
+ throw new Error(
225
+ `the index object does not decode: the package was exported by an older e3 SDK — re-export it with the current one ` +
226
+ `(${err instanceof Error ? err.message : String(err)})`,
227
+ );
228
+ }
229
+ }
230
+
203
231
  /**
204
232
  * How a migration says what it changes.
205
233
  *
package/src/release.ts CHANGED
@@ -17,7 +17,7 @@
17
17
  */
18
18
 
19
19
  /** The release of e3 this build is. */
20
- export const E3_RELEASE = '1.0.83';
20
+ export const E3_RELEASE = '1.0.85';
21
21
 
22
22
  /** A semantic version: its core, a pre-release, and build metadata. */
23
23
  const RELEASE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
package/src/repository.ts CHANGED
@@ -34,6 +34,24 @@ export const RepositoryRecordType = StructType({
34
34
 
35
35
  export type RepositoryRecord = ValueTypeOf<typeof RepositoryRecordType>;
36
36
 
37
+ /**
38
+ * A store upgrade under way in a repository, kept beside the repository
39
+ * record: the step, the release of e3 applying it, and where its last part
40
+ * stopped. A step applies in parts, and the next part takes it up from here,
41
+ * in this process or another; the record lists the step only once it is done.
42
+ */
43
+ export const RepositoryUpgradeProgressType = StructType({
44
+ /** The step's name */
45
+ step: StringType,
46
+ /** The release of e3 applying it */
47
+ release: StringType,
48
+ /** Where its last part stopped: the step's own cursor, which the next part
49
+ * takes it up from */
50
+ cursor: StringType,
51
+ });
52
+
53
+ export type RepositoryUpgradeProgress = ValueTypeOf<typeof RepositoryUpgradeProgressType>;
54
+
37
55
  /**
38
56
  * Where a repository is in its lifecycle.
39
57
  */
@@ -18,9 +18,9 @@ import { variant } from '@elaraai/east';
18
18
  import { withRunnerLifeline, type RunnerValue } from './runner.js';
19
19
 
20
20
  const KNOWN: Array<[string, RunnerValue, string]> = [
21
- ['east_node', variant('east_node', { platforms: ['@elaraai/east-node-std'] }), 'east-node'],
22
- ['east_py', variant('east_py', { platforms: ['east-py-std'] }), 'east-py'],
23
- ['east_c', variant('east_c', { platforms: ['east-c-std'] }), 'east-c'],
21
+ ['east_node', variant('east_node', { platforms: ['@elaraai/east-node-std'], decode: variant('lazy', null) }), 'east-node'],
22
+ ['east_py', variant('east_py', { platforms: ['east-py-std'], decode: variant('lazy', null) }), 'east-py'],
23
+ ['east_c', variant('east_c', { platforms: ['east-c-std'], decode: variant('lazy', null) }), 'east-c'],
24
24
  ];
25
25
 
26
26
  describe('withRunnerLifeline', () => {
@@ -38,7 +38,7 @@ describe('withRunnerLifeline', () => {
38
38
  });
39
39
 
40
40
  it('is a no-op on a degenerate argv and does not mutate its input', () => {
41
- const runner = variant('east_c', { platforms: [] });
41
+ const runner = variant('east_c', { platforms: [], decode: variant('lazy', null) });
42
42
  assert.deepStrictEqual(withRunnerLifeline(runner, ['east-c']), ['east-c']);
43
43
  const args = ['east-c', 'exec', 'unit.beast2'];
44
44
  const before = [...args];
package/src/runner.ts CHANGED
@@ -11,23 +11,26 @@
11
11
  * its `exec` command; `custom` names a command of the author's own.
12
12
  */
13
13
 
14
- import { VariantType, StructType, ArrayType, StringType, ValueTypeOf } from '@elaraai/east';
14
+ import { VariantType, StructType, ArrayType, StringType, UnitDecodeType, ValueTypeOf } from '@elaraai/east';
15
15
 
16
16
  /**
17
17
  * Wire representation of a runner — shared by tasks, functions and records.
18
18
  *
19
- * A known-runtime tag names a stock runner, and `platforms` names the platform
20
- * packages the units it runs load. `custom` carries a command, which e3 runs
21
- * with the arguments of a stock runner's `run`: `-i` for each input, `-o` for
22
- * the output, then the program's file. A custom task's body is its own
23
- * command, and its runner's is empty. Package authors can already execute
24
- * arbitrary commands via custom tasks, so `custom` grants no capability that
25
- * tasks don't have.
19
+ * A known-runtime tag names a stock runner: `platforms` names the platform
20
+ * packages the units it runs load, and `decode` how it reads a run unit's
21
+ * inputs — each collection `lazy`, a segment decoded as the program reaches
22
+ * it, or every input decoded `whole` before the program runs — which e3 writes
23
+ * into each run unit it stages for the runner. `custom` carries a command,
24
+ * which e3 runs with the arguments of a stock runner's `run`: `-i` for each
25
+ * input, `-o` for the output, then the program's file. A custom task's body is
26
+ * its own command, and its runner's is empty. Package authors can already
27
+ * execute arbitrary commands via custom tasks, so `custom` grants no
28
+ * capability that tasks don't have.
26
29
  */
27
30
  export const RunnerType = VariantType({
28
- east_node: StructType({ platforms: ArrayType(StringType) }),
29
- east_py: StructType({ platforms: ArrayType(StringType) }),
30
- east_c: StructType({ platforms: ArrayType(StringType) }),
31
+ east_node: StructType({ platforms: ArrayType(StringType), decode: UnitDecodeType }),
32
+ east_py: StructType({ platforms: ArrayType(StringType), decode: UnitDecodeType }),
33
+ east_c: StructType({ platforms: ArrayType(StringType), decode: UnitDecodeType }),
31
34
  custom: StructType({ command: ArrayType(StringType) }),
32
35
  });
33
36
  export type RunnerType = typeof RunnerType;