@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.
- package/dist/src/api.d.ts +583 -17
- package/dist/src/api.d.ts.map +1 -1
- package/dist/src/api.js +208 -22
- package/dist/src/api.js.map +1 -1
- package/dist/src/dataflow.d.ts +115 -12
- package/dist/src/dataflow.d.ts.map +1 -1
- package/dist/src/dataflow.js +151 -15
- package/dist/src/dataflow.js.map +1 -1
- package/dist/src/dataflow.spec.js +41 -7
- package/dist/src/dataflow.spec.js.map +1 -1
- package/dist/src/dataset-blob.d.ts +19 -2
- package/dist/src/dataset-blob.d.ts.map +1 -1
- package/dist/src/dataset-blob.js +100 -21
- package/dist/src/dataset-blob.js.map +1 -1
- package/dist/src/dataset-blob.spec.js +132 -0
- package/dist/src/dataset-blob.spec.js.map +1 -1
- package/dist/src/execution.d.ts +68 -8
- package/dist/src/execution.d.ts.map +1 -1
- package/dist/src/execution.js +49 -8
- package/dist/src/execution.js.map +1 -1
- package/dist/src/execution.spec.js +31 -9
- package/dist/src/execution.spec.js.map +1 -1
- package/dist/src/function.d.ts +12 -0
- package/dist/src/function.d.ts.map +1 -1
- package/dist/src/index.d.ts +9 -8
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +15 -12
- package/dist/src/index.js.map +1 -1
- package/dist/src/names.d.ts +9 -0
- package/dist/src/names.d.ts.map +1 -1
- package/dist/src/names.js.map +1 -1
- package/dist/src/pieces.d.ts +47 -0
- package/dist/src/pieces.d.ts.map +1 -0
- package/dist/src/pieces.js +17 -0
- package/dist/src/pieces.js.map +1 -0
- package/dist/src/pieces.spec.d.ts +6 -0
- package/dist/src/pieces.spec.d.ts.map +1 -0
- package/dist/src/pieces.spec.js +20 -0
- package/dist/src/pieces.spec.js.map +1 -0
- package/dist/src/record.d.ts +52 -0
- package/dist/src/record.d.ts.map +1 -1
- package/dist/src/record.js +25 -0
- package/dist/src/record.js.map +1 -1
- package/dist/src/record.spec.js +43 -6
- package/dist/src/record.spec.js.map +1 -1
- package/dist/src/release.d.ts +1 -1
- package/dist/src/release.js +1 -1
- package/dist/src/repository.d.ts +16 -0
- package/dist/src/repository.d.ts.map +1 -1
- package/dist/src/repository.js +15 -0
- package/dist/src/repository.js.map +1 -1
- package/dist/src/runner.d.ts +22 -7
- package/dist/src/runner.d.ts.map +1 -1
- package/dist/src/runner.js +14 -11
- package/dist/src/runner.js.map +1 -1
- package/dist/src/runner.spec.js +4 -4
- package/dist/src/runner.spec.js.map +1 -1
- package/dist/src/structure.d.ts +27 -0
- package/dist/src/structure.d.ts.map +1 -1
- package/dist/src/structure.js +59 -18
- package/dist/src/structure.js.map +1 -1
- package/dist/src/structure.spec.d.ts +6 -0
- package/dist/src/structure.spec.d.ts.map +1 -0
- package/dist/src/structure.spec.js +57 -0
- package/dist/src/structure.spec.js.map +1 -0
- package/dist/src/task.d.ts +13 -1
- package/dist/src/task.d.ts.map +1 -1
- package/dist/src/task.js +1 -1
- package/dist/src/task.spec.js +25 -4
- package/dist/src/task.spec.js.map +1 -1
- package/package.json +3 -2
- package/src/api.ts +225 -21
- package/src/dataflow.spec.ts +49 -7
- package/src/dataflow.ts +160 -14
- package/src/dataset-blob.spec.ts +147 -0
- package/src/dataset-blob.ts +97 -20
- package/src/execution.spec.ts +31 -9
- package/src/execution.ts +52 -7
- package/src/index.ts +39 -2
- package/src/names.ts +12 -0
- package/src/pieces.spec.ts +23 -0
- package/src/pieces.ts +49 -0
- package/src/record.spec.ts +51 -5
- package/src/record.ts +28 -0
- package/src/release.ts +1 -1
- package/src/repository.ts +18 -0
- package/src/runner.spec.ts +4 -4
- package/src/runner.ts +14 -11
- package/src/structure.spec.ts +65 -0
- package/src/structure.ts +64 -21
- package/src/task.spec.ts +29 -3
- 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(
|
|
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(
|
|
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),
|