@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
@@ -0,0 +1,65 @@
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
+ * Keypaths: `parseKeypath` reads back what `pathToString` writes, whatever a
8
+ * field is named, with no structure to check it against; `parsePath` reads the
9
+ * same syntax and checks each field against a structure as it reads it.
10
+ */
11
+
12
+ import { describe, it } from 'node:test';
13
+ import assert from 'node:assert/strict';
14
+ import { IntegerType, equalFor, printFor, toEastTypeValue, variant } from '@elaraai/east';
15
+ import { TreePathType, parseKeypath, parsePath, pathToString, type Structure, type TreePath } from './structure.js';
16
+
17
+ const equalPath = equalFor(TreePathType);
18
+ const printPath = printFor(TreePathType);
19
+
20
+ describe('parseKeypath', () => {
21
+ it('reads back what pathToString writes, whatever a field is named', () => {
22
+ const names = ['inputs', 'my field', 'a.b', 'a/b', 'back`tick', 'back\\slash', 'café', '1st', '', '`'];
23
+ for (const name of names) {
24
+ const path: TreePath = [variant('field', 'inputs'), variant('field', name)];
25
+ const printed = pathToString(path);
26
+ if (name === '') {
27
+ // An empty field name prints as nothing a keypath can read.
28
+ assert.throws(() => parseKeypath(printed));
29
+ continue;
30
+ }
31
+ const read = parseKeypath(printed);
32
+ assert.ok(equalPath(read, path), `${JSON.stringify(name)}: ${printed} read back as ${printPath(read)}`);
33
+ }
34
+ });
35
+
36
+ it('reads the empty string as the root', () => {
37
+ assert.ok(equalPath(parseKeypath(''), []));
38
+ });
39
+
40
+ it('refuses a string that is not a keypath, naming where', () => {
41
+ assert.throws(() => parseKeypath('inputs.x'), { message: "parseKeypath: unexpected character at position 0: 'i'" });
42
+ assert.throws(() => parseKeypath('.inputs.'), { message: "parseKeypath: expected identifier after '.' at position 8" });
43
+ assert.throws(() => parseKeypath('.inputs!'), { message: "parseKeypath: unexpected character at position 7: '!'" });
44
+ });
45
+ });
46
+
47
+ describe('parsePath', () => {
48
+ const structure: Structure = variant('struct', new Map([
49
+ ['inputs', variant('struct', new Map([
50
+ ['sales', variant('value', { type: toEastTypeValue(IntegerType), writable: true })],
51
+ ]))],
52
+ ]));
53
+
54
+ it('reads a keypath, and the structure at it', () => {
55
+ const { path, structure: at } = parsePath('.inputs.sales', structure);
56
+ assert.ok(equalPath(path, [variant('field', 'inputs'), variant('field', 'sales')]));
57
+ assert.equal(at.type, 'value');
58
+ });
59
+
60
+ it('refuses a field the structure lacks before a malformed one after it, and a path into a dataset', () => {
61
+ assert.throws(() => parsePath('.nope!', structure), { message: "parsePath: field 'nope' not found at ''. Available: inputs" });
62
+ assert.throws(() => parsePath('.inputs!', structure), { message: "parsePath: unexpected character at position 7: '!'" });
63
+ assert.throws(() => parsePath('.inputs.sales.more', structure), { message: "parsePath: cannot descend into dataset at '.inputs.sales'" });
64
+ });
65
+ });
package/src/structure.ts CHANGED
@@ -181,10 +181,69 @@ export interface ParsePathResult {
181
181
  * ```
182
182
  */
183
183
  export function parsePath(pathStr: string, structure: Structure): ParsePathResult {
184
- if (pathStr === '') return { path: [], structure };
185
-
186
184
  const segments: TreePath = [];
187
185
  let currentStructure = structure;
186
+
187
+ // Each field is checked as it is read, so a field the structure lacks is
188
+ // refused before a malformed one after it.
189
+ for (const fieldName of keypathFields(pathStr, 'parsePath')) {
190
+ if (currentStructure.type === 'value') {
191
+ throw new Error(`parsePath: cannot descend into dataset at '${pathToString(segments)}'`);
192
+ }
193
+
194
+ // currentStructure.type === 'struct' (only other option after 'value' check)
195
+ const fields = currentStructure.value;
196
+ const childStructure = fields.get(fieldName);
197
+ if (childStructure === undefined) {
198
+ const available = [...fields.keys()].map(k => printIdentifier(k)).join(', ');
199
+ throw new Error(`parsePath: field '${fieldName}' not found at '${pathToString(segments)}'. Available: ${available}`);
200
+ }
201
+ segments.push(variant('field', fieldName));
202
+ currentStructure = childStructure;
203
+ }
204
+
205
+ return { path: segments, structure: currentStructure };
206
+ }
207
+
208
+ /**
209
+ * Parses an East keypath string into a path, with no structure to check it
210
+ * against: the inverse of {@link pathToString}.
211
+ *
212
+ * @remarks
213
+ * The syntax is {@link parsePath}'s: `.field` for a simple identifier, and
214
+ * `` .`field` `` for one that needs quoting, with `` \` `` for a literal
215
+ * backtick and `\\` for a backslash. Whether the path names anything is the
216
+ * caller's to judge, as a status asked for named datasets leaves out a path
217
+ * the workspace does not hold.
218
+ *
219
+ * @param pathStr - A keypath string (e.g., ".inputs.sales", or ".inputs.`my field`")
220
+ * @returns The path; the root's, `[]`, for the empty string
221
+ *
222
+ * @throws {Error} When the string is not a keypath, such as one that does not
223
+ * start with `.`
224
+ *
225
+ * @example
226
+ * ```ts
227
+ * parseKeypath('.inputs.sales');
228
+ * // [field('inputs'), field('sales')]
229
+ *
230
+ * parseKeypath(pathToString([variant('field', 'inputs'), variant('field', 'my field')]));
231
+ * // [field('inputs'), field('my field')]
232
+ * ```
233
+ */
234
+ export function parseKeypath(pathStr: string): TreePath {
235
+ return [...keypathFields(pathStr, 'parseKeypath')].map((fieldName) => variant('field', fieldName));
236
+ }
237
+
238
+ /**
239
+ * Reads a keypath string's field names in order, refusing a malformed one when
240
+ * it reaches it.
241
+ *
242
+ * @param pathStr - The keypath string
243
+ * @param caller - The function a refusal names
244
+ * @internal
245
+ */
246
+ function* keypathFields(pathStr: string, caller: string): Generator<string> {
188
247
  let pos = 0;
189
248
 
190
249
  while (pos < pathStr.length) {
@@ -221,29 +280,13 @@ export function parsePath(pathStr: string, structure: Structure): ParsePathResul
221
280
  }
222
281
 
223
282
  if (fieldName.length === 0) {
224
- throw new Error(`parsePath: expected identifier after '.' at position ${pos}`);
225
- }
226
-
227
- // Validate against structure
228
- if (currentStructure.type === 'value') {
229
- throw new Error(`parsePath: cannot descend into dataset at '${pathToString(segments)}'`);
283
+ throw new Error(`${caller}: expected identifier after '.' at position ${pos}`);
230
284
  }
231
-
232
- // currentStructure.type === 'struct' (only other option after 'value' check)
233
- const fields = currentStructure.value;
234
- const childStructure = fields.get(fieldName);
235
- if (childStructure === undefined) {
236
- const available = [...fields.keys()].map(k => printIdentifier(k)).join(', ');
237
- throw new Error(`parsePath: field '${fieldName}' not found at '${pathToString(segments)}'. Available: ${available}`);
238
- }
239
- segments.push(variant('field', fieldName));
240
- currentStructure = childStructure;
285
+ yield fieldName;
241
286
  } else {
242
- throw new Error(`parsePath: unexpected character at position ${pos}: '${pathStr[pos]}'`);
287
+ throw new Error(`${caller}: unexpected character at position ${pos}: '${pathStr[pos]}'`);
243
288
  }
244
289
  }
245
-
246
- return { path: segments, structure: currentStructure };
247
290
  }
248
291
 
249
292
  /**
package/src/task.spec.ts CHANGED
@@ -9,10 +9,14 @@
9
9
 
10
10
  import { describe, it } from 'node:test';
11
11
  import assert from 'node:assert/strict';
12
- import { ArrayType, BlobType, IntegerType, OptionType, StringType, StructType, encodeBeast2For, none, some, variant } from '@elaraai/east';
12
+ import { ArrayType, BlobType, IntegerType, OptionType, StringType, StructType, VariantType, encodeBeast2For, none, some, variant } from '@elaraai/east';
13
13
  import {
14
14
  TASK_OBJECT_KIND,
15
+ TaskBodyType,
16
+ TaskInputType,
15
17
  TaskObjectType,
18
+ TaskOutputType,
19
+ TaskRoleType,
16
20
  decodeTaskObject,
17
21
  type TaskObject,
18
22
  } from './task.js';
@@ -26,7 +30,7 @@ describe('TaskObjectType', () => {
26
30
  const base: TaskObject = {
27
31
  kind: TASK_OBJECT_KIND,
28
32
  body: variant('east', { program: 'a'.repeat(64) }),
29
- runner: variant('east_node', { platforms: ['@elaraai/east-node-std'] }),
33
+ runner: variant('east_node', { platforms: ['@elaraai/east-node-std'], decode: variant('lazy', null) }),
30
34
  inputs: [{ path: sales, partition: none }],
31
35
  output: { path: totals, kind: variant('value', null) },
32
36
  role: variant('data', null),
@@ -49,6 +53,7 @@ describe('TaskObjectType', () => {
49
53
  },
50
54
  { ...base, output: { path: totals, kind: variant('fold', { zero: 'e'.repeat(64), combine: 'f'.repeat(64) }) } },
51
55
  { ...base, output: { path: totals, kind: variant('set', null) } },
56
+ { ...base, runner: variant('east_py', { platforms: ['east-py-std'], decode: variant('whole', null) }) },
52
57
  { ...base, role: variant('ui', { paths: [sales], functions: ['forecast'], records: ['plans'], pages: [rates] }) },
53
58
  ];
54
59
  const encode = encodeBeast2For(TaskObjectType);
@@ -71,12 +76,33 @@ describe('TaskObjectType', () => {
71
76
  output: totals,
72
77
  kind: none,
73
78
  metadata: none,
74
- runner: variant('east_node', { platforms: [] }),
79
+ runner: variant('east_node', { platforms: [], decode: variant('lazy', null) }),
75
80
  environment: none,
76
81
  });
77
82
  assert.throws(() => decodeTaskObject(older), /exported by an older e3 SDK — re-export it with the current one/);
78
83
  });
79
84
 
85
+ it('refuses a task object whose runner predates its decode, saying to re-export the package', () => {
86
+ // A stock runner as a package exported before runners said how they read
87
+ // a program's inputs: its platforms alone.
88
+ const PreDecodeRunnerType = VariantType({
89
+ east_node: StructType({ platforms: ArrayType(StringType) }),
90
+ east_py: StructType({ platforms: ArrayType(StringType) }),
91
+ east_c: StructType({ platforms: ArrayType(StringType) }),
92
+ custom: StructType({ command: ArrayType(StringType) }),
93
+ });
94
+ const older = encodeBeast2For(StructType({
95
+ kind: StringType,
96
+ body: TaskBodyType,
97
+ runner: PreDecodeRunnerType,
98
+ inputs: ArrayType(TaskInputType),
99
+ output: TaskOutputType,
100
+ role: TaskRoleType,
101
+ environment: OptionType(StringType),
102
+ }))({ ...base, runner: variant('east_node', { platforms: ['@elaraai/east-node-std'] }) });
103
+ assert.throws(() => decodeTaskObject(older), /exported by an older e3 SDK — re-export it with the current one/);
104
+ });
105
+
80
106
  it('refuses an object of the task shape that carries another kind tag', () => {
81
107
  assert.throws(() => decodeTaskObject(encodeBeast2For(TaskObjectType)({ ...base, kind: '$plan' })), /its kind is '\$plan', not '\$task'/);
82
108
  });
package/src/task.ts CHANGED
@@ -132,7 +132,7 @@ export type TaskRole = ValueTypeOf<typeof TaskRoleType>;
132
132
  * const task: TaskObject = {
133
133
  * kind: TASK_OBJECT_KIND,
134
134
  * body: variant('east', { program: '5e7a3b...' }),
135
- * runner: variant('east_node', { platforms: ['@elaraai/east-node-std'] }),
135
+ * runner: variant('east_node', { platforms: ['@elaraai/east-node-std'], decode: variant('lazy', null) }),
136
136
  * inputs: [{ path: [variant('field', 'inputs'), variant('field', 'sales')], partition: none }],
137
137
  * output: { path: [variant('field', 'tasks'), variant('field', 'totals'), variant('field', 'output')], kind: variant('value', null) },
138
138
  * role: variant('data', null),