@elaraai/e3-types 1.0.76 → 1.0.78
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 +25 -0
- package/dist/src/api.d.ts.map +1 -1
- package/dist/src/api.js +18 -0
- package/dist/src/api.js.map +1 -1
- package/dist/src/dataflow.d.ts +10 -6
- package/dist/src/dataflow.d.ts.map +1 -1
- package/dist/src/dataflow.js.map +1 -1
- package/dist/src/dataset-type.d.ts +71 -0
- package/dist/src/dataset-type.d.ts.map +1 -0
- package/dist/src/dataset-type.js +101 -0
- package/dist/src/dataset-type.js.map +1 -0
- package/dist/src/execution.d.ts +17 -0
- package/dist/src/execution.d.ts.map +1 -1
- package/dist/src/index.d.ts +8 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +10 -5
- package/dist/src/index.js.map +1 -1
- package/dist/src/package.d.ts +33 -3
- package/dist/src/package.d.ts.map +1 -1
- package/dist/src/package.js +47 -8
- package/dist/src/package.js.map +1 -1
- package/dist/src/runner.d.ts +35 -9
- package/dist/src/runner.d.ts.map +1 -1
- package/dist/src/runner.js +46 -13
- package/dist/src/runner.js.map +1 -1
- package/dist/src/runner.spec.js +50 -1
- package/dist/src/runner.spec.js.map +1 -1
- package/dist/src/stream.d.ts +97 -0
- package/dist/src/stream.d.ts.map +1 -0
- package/dist/src/stream.js +134 -0
- package/dist/src/stream.js.map +1 -0
- package/dist/src/stream.spec.d.ts +6 -0
- package/dist/src/stream.spec.d.ts.map +1 -0
- package/dist/src/stream.spec.js +60 -0
- package/dist/src/stream.spec.js.map +1 -0
- package/dist/src/task.d.ts +198 -10
- package/dist/src/task.d.ts.map +1 -1
- package/dist/src/task.js +340 -11
- package/dist/src/task.js.map +1 -1
- package/dist/src/task.spec.d.ts +6 -0
- package/dist/src/task.spec.d.ts.map +1 -0
- package/dist/src/task.spec.js +169 -0
- package/dist/src/task.spec.js.map +1 -0
- package/dist/src/transfer.d.ts +70 -3
- package/dist/src/transfer.d.ts.map +1 -1
- package/dist/src/transfer.js +76 -3
- package/dist/src/transfer.js.map +1 -1
- package/dist/src/transfer.spec.d.ts +6 -0
- package/dist/src/transfer.spec.d.ts.map +1 -0
- package/dist/src/transfer.spec.js +75 -0
- package/dist/src/transfer.spec.js.map +1 -0
- package/package.json +2 -2
- package/src/api.ts +18 -0
- package/src/dataflow.ts +10 -6
- package/src/dataset-type.ts +134 -0
- package/src/execution.ts +18 -0
- package/src/index.ts +37 -0
- package/src/package.ts +50 -8
- package/src/runner.spec.ts +56 -1
- package/src/runner.ts +45 -13
- package/src/stream.spec.ts +88 -0
- package/src/stream.ts +170 -0
- package/src/task.spec.ts +208 -0
- package/src/task.ts +374 -13
- package/src/transfer.spec.ts +98 -0
- package/src/transfer.ts +84 -3
package/dist/src/transfer.d.ts
CHANGED
|
@@ -7,8 +7,46 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Used by both the API server and client for large dataset uploads
|
|
9
9
|
* that exceed inline body size limits.
|
|
10
|
+
*
|
|
11
|
+
* Two protocol versions share these types. A client names the version it speaks
|
|
12
|
+
* with `?protocol=N` on the init and commit requests (a request without it is
|
|
13
|
+
* version 1), and a server answers only in the forms that version understands —
|
|
14
|
+
* every case version 2 adds sorts after the version-1 cases, so the tags a
|
|
15
|
+
* version-1 client decodes keep their numbers.
|
|
16
|
+
*/
|
|
17
|
+
import { VariantType, StructType, StringType, IntegerType, NullType, DictType, type ValueTypeOf } from '@elaraai/east';
|
|
18
|
+
/**
|
|
19
|
+
* The dataset transfer protocol version this build speaks.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* Version 2 lets the server plan an upload as parts — each sent to its own URL
|
|
23
|
+
* with the request headers the server names, so an object store can bind a
|
|
24
|
+
* checksum to a single PUT or take a multipart upload larger than one PUT
|
|
25
|
+
* allows — and lets a commit answer `processing` while the server verifies the
|
|
26
|
+
* bytes, for the client to poll.
|
|
27
|
+
*/
|
|
28
|
+
export declare const TRANSFER_PROTOCOL_VERSION = 2;
|
|
29
|
+
/**
|
|
30
|
+
* How many parts an upload of `size` bytes has when planned with `partBytes`.
|
|
31
|
+
*
|
|
32
|
+
* @param size - The upload's size in bytes
|
|
33
|
+
* @param partBytes - The size of every part but the last
|
|
34
|
+
* @returns The part count — at least 1
|
|
35
|
+
*/
|
|
36
|
+
export declare function transferPartCount(size: number | bigint, partBytes: number | bigint): number;
|
|
37
|
+
/**
|
|
38
|
+
* The byte range part `part` of an upload covers.
|
|
39
|
+
*
|
|
40
|
+
* @param size - The upload's size in bytes
|
|
41
|
+
* @param partBytes - The size of every part but the last
|
|
42
|
+
* @param part - The part's number, from 1
|
|
43
|
+
* @returns The half-open range `[start, end)`, or `null` when the upload has no
|
|
44
|
+
* such part
|
|
10
45
|
*/
|
|
11
|
-
|
|
46
|
+
export declare function transferPartRange(size: number | bigint, partBytes: number | bigint, part: number): {
|
|
47
|
+
start: number;
|
|
48
|
+
end: number;
|
|
49
|
+
} | null;
|
|
12
50
|
/**
|
|
13
51
|
* Transfer upload init request.
|
|
14
52
|
*
|
|
@@ -26,7 +64,15 @@ export type TransferUploadRequest = ValueTypeOf<typeof TransferUploadRequestType
|
|
|
26
64
|
* Transfer upload init response.
|
|
27
65
|
*
|
|
28
66
|
* - `completed`: Object already exists (dedup), dataset ref updated
|
|
29
|
-
* - `upload`: Staging slot created
|
|
67
|
+
* - `upload`: Staging slot created; the client PUTs every byte to `uploadUrl`,
|
|
68
|
+
* then commits
|
|
69
|
+
* - `upload_parts` (protocol 2): the client sends the bytes as parts of
|
|
70
|
+
* `partBytes` bytes each (the last part holds the remainder, and an upload no
|
|
71
|
+
* larger than `partBytes` is one part). Part `n` (1-based) is the byte range
|
|
72
|
+
* `[(n - 1) * partBytes, min(size, n * partBytes))`; its URL and required
|
|
73
|
+
* headers come from `GET …/upload/<id>/parts/<n>`. Parts may be sent in any
|
|
74
|
+
* order and concurrently, and re-sending a part replaces it. The client
|
|
75
|
+
* commits once every part is sent.
|
|
30
76
|
*/
|
|
31
77
|
export declare const TransferUploadResponseType: VariantType<{
|
|
32
78
|
readonly completed: NullType;
|
|
@@ -34,19 +80,40 @@ export declare const TransferUploadResponseType: VariantType<{
|
|
|
34
80
|
readonly id: StringType;
|
|
35
81
|
readonly uploadUrl: StringType;
|
|
36
82
|
}>;
|
|
83
|
+
readonly upload_parts: StructType<{
|
|
84
|
+
readonly id: StringType;
|
|
85
|
+
readonly partBytes: IntegerType;
|
|
86
|
+
}>;
|
|
37
87
|
}>;
|
|
38
88
|
export type TransferUploadResponse = ValueTypeOf<typeof TransferUploadResponseType>;
|
|
39
89
|
/**
|
|
40
|
-
*
|
|
90
|
+
* Where and how to send one part of a protocol-2 upload.
|
|
91
|
+
*
|
|
92
|
+
* @property url - The URL the client PUTs the part's bytes to, without an
|
|
93
|
+
* `Authorization` header (it may be a presigned object-store URL)
|
|
94
|
+
* @property headers - Request headers the PUT must carry exactly as given — a
|
|
95
|
+
* presigned URL can sign them, e.g. the checksum an object store verifies the
|
|
96
|
+
* bytes against
|
|
97
|
+
*/
|
|
98
|
+
export declare const TransferPartResponseType: StructType<{
|
|
99
|
+
readonly url: StringType;
|
|
100
|
+
readonly headers: DictType<StringType, StringType>;
|
|
101
|
+
}>;
|
|
102
|
+
export type TransferPartResponse = ValueTypeOf<typeof TransferPartResponseType>;
|
|
103
|
+
/**
|
|
104
|
+
* Transfer done response — the commit's answer, and the poll's.
|
|
41
105
|
*
|
|
42
106
|
* - `completed`: Hash verified, object stored, dataset ref updated
|
|
43
107
|
* - `error`: Hash mismatch or other failure
|
|
108
|
+
* - `processing` (protocol 2): the server is still verifying the bytes; poll
|
|
109
|
+
* `GET …/upload/<id>` until it answers `completed` or `error`
|
|
44
110
|
*/
|
|
45
111
|
export declare const TransferDoneResponseType: VariantType<{
|
|
46
112
|
readonly completed: NullType;
|
|
47
113
|
readonly error: StructType<{
|
|
48
114
|
readonly message: StringType;
|
|
49
115
|
}>;
|
|
116
|
+
readonly processing: NullType;
|
|
50
117
|
}>;
|
|
51
118
|
export type TransferDoneResponse = ValueTypeOf<typeof TransferDoneResponseType>;
|
|
52
119
|
//# sourceMappingURL=transfer.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transfer.d.ts","sourceRoot":"","sources":["../../src/transfer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH
|
|
1
|
+
{"version":3,"file":"transfer.d.ts","sourceRoot":"","sources":["../../src/transfer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAEvH;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,IAAI,CAAC;AAE3C;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAI3F;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,MAAM,GAAG,MAAM,EACrB,SAAS,EAAE,MAAM,GAAG,MAAM,EAC1B,IAAI,EAAE,MAAM,GACX;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAKvC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB;;;EAGpC,CAAC;AACH,MAAM,MAAM,qBAAqB,GAAG,WAAW,CAAC,OAAO,yBAAyB,CAAC,CAAC;AAElF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,0BAA0B;;;;;;;;;;EAUrC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,WAAW,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB;;;EAGnC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,WAAW,CAAC,OAAO,wBAAwB,CAAC,CAAC;AAEhF;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB;;;;;;EAMnC,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,WAAW,CAAC,OAAO,wBAAwB,CAAC,CAAC"}
|
package/dist/src/transfer.js
CHANGED
|
@@ -7,8 +7,53 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Used by both the API server and client for large dataset uploads
|
|
9
9
|
* that exceed inline body size limits.
|
|
10
|
+
*
|
|
11
|
+
* Two protocol versions share these types. A client names the version it speaks
|
|
12
|
+
* with `?protocol=N` on the init and commit requests (a request without it is
|
|
13
|
+
* version 1), and a server answers only in the forms that version understands —
|
|
14
|
+
* every case version 2 adds sorts after the version-1 cases, so the tags a
|
|
15
|
+
* version-1 client decodes keep their numbers.
|
|
16
|
+
*/
|
|
17
|
+
import { VariantType, StructType, StringType, IntegerType, NullType, DictType } from '@elaraai/east';
|
|
18
|
+
/**
|
|
19
|
+
* The dataset transfer protocol version this build speaks.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* Version 2 lets the server plan an upload as parts — each sent to its own URL
|
|
23
|
+
* with the request headers the server names, so an object store can bind a
|
|
24
|
+
* checksum to a single PUT or take a multipart upload larger than one PUT
|
|
25
|
+
* allows — and lets a commit answer `processing` while the server verifies the
|
|
26
|
+
* bytes, for the client to poll.
|
|
27
|
+
*/
|
|
28
|
+
export const TRANSFER_PROTOCOL_VERSION = 2;
|
|
29
|
+
/**
|
|
30
|
+
* How many parts an upload of `size` bytes has when planned with `partBytes`.
|
|
31
|
+
*
|
|
32
|
+
* @param size - The upload's size in bytes
|
|
33
|
+
* @param partBytes - The size of every part but the last
|
|
34
|
+
* @returns The part count — at least 1
|
|
35
|
+
*/
|
|
36
|
+
export function transferPartCount(size, partBytes) {
|
|
37
|
+
const total = BigInt(size);
|
|
38
|
+
const each = BigInt(partBytes);
|
|
39
|
+
return total <= each ? 1 : Number((total + each - 1n) / each);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* The byte range part `part` of an upload covers.
|
|
43
|
+
*
|
|
44
|
+
* @param size - The upload's size in bytes
|
|
45
|
+
* @param partBytes - The size of every part but the last
|
|
46
|
+
* @param part - The part's number, from 1
|
|
47
|
+
* @returns The half-open range `[start, end)`, or `null` when the upload has no
|
|
48
|
+
* such part
|
|
10
49
|
*/
|
|
11
|
-
|
|
50
|
+
export function transferPartRange(size, partBytes, part) {
|
|
51
|
+
if (!Number.isInteger(part) || part < 1 || part > transferPartCount(size, partBytes))
|
|
52
|
+
return null;
|
|
53
|
+
const start = BigInt(part - 1) * BigInt(partBytes);
|
|
54
|
+
const end = start + BigInt(partBytes) < BigInt(size) ? start + BigInt(partBytes) : BigInt(size);
|
|
55
|
+
return { start: Number(start), end: Number(end) };
|
|
56
|
+
}
|
|
12
57
|
/**
|
|
13
58
|
* Transfer upload init request.
|
|
14
59
|
*
|
|
@@ -25,7 +70,15 @@ export const TransferUploadRequestType = StructType({
|
|
|
25
70
|
* Transfer upload init response.
|
|
26
71
|
*
|
|
27
72
|
* - `completed`: Object already exists (dedup), dataset ref updated
|
|
28
|
-
* - `upload`: Staging slot created
|
|
73
|
+
* - `upload`: Staging slot created; the client PUTs every byte to `uploadUrl`,
|
|
74
|
+
* then commits
|
|
75
|
+
* - `upload_parts` (protocol 2): the client sends the bytes as parts of
|
|
76
|
+
* `partBytes` bytes each (the last part holds the remainder, and an upload no
|
|
77
|
+
* larger than `partBytes` is one part). Part `n` (1-based) is the byte range
|
|
78
|
+
* `[(n - 1) * partBytes, min(size, n * partBytes))`; its URL and required
|
|
79
|
+
* headers come from `GET …/upload/<id>/parts/<n>`. Parts may be sent in any
|
|
80
|
+
* order and concurrently, and re-sending a part replaces it. The client
|
|
81
|
+
* commits once every part is sent.
|
|
29
82
|
*/
|
|
30
83
|
export const TransferUploadResponseType = VariantType({
|
|
31
84
|
completed: NullType,
|
|
@@ -33,17 +86,37 @@ export const TransferUploadResponseType = VariantType({
|
|
|
33
86
|
id: StringType,
|
|
34
87
|
uploadUrl: StringType,
|
|
35
88
|
}),
|
|
89
|
+
upload_parts: StructType({
|
|
90
|
+
id: StringType,
|
|
91
|
+
partBytes: IntegerType,
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* Where and how to send one part of a protocol-2 upload.
|
|
96
|
+
*
|
|
97
|
+
* @property url - The URL the client PUTs the part's bytes to, without an
|
|
98
|
+
* `Authorization` header (it may be a presigned object-store URL)
|
|
99
|
+
* @property headers - Request headers the PUT must carry exactly as given — a
|
|
100
|
+
* presigned URL can sign them, e.g. the checksum an object store verifies the
|
|
101
|
+
* bytes against
|
|
102
|
+
*/
|
|
103
|
+
export const TransferPartResponseType = StructType({
|
|
104
|
+
url: StringType,
|
|
105
|
+
headers: DictType(StringType, StringType),
|
|
36
106
|
});
|
|
37
107
|
/**
|
|
38
|
-
* Transfer done response.
|
|
108
|
+
* Transfer done response — the commit's answer, and the poll's.
|
|
39
109
|
*
|
|
40
110
|
* - `completed`: Hash verified, object stored, dataset ref updated
|
|
41
111
|
* - `error`: Hash mismatch or other failure
|
|
112
|
+
* - `processing` (protocol 2): the server is still verifying the bytes; poll
|
|
113
|
+
* `GET …/upload/<id>` until it answers `completed` or `error`
|
|
42
114
|
*/
|
|
43
115
|
export const TransferDoneResponseType = VariantType({
|
|
44
116
|
completed: NullType,
|
|
45
117
|
error: StructType({
|
|
46
118
|
message: StringType,
|
|
47
119
|
}),
|
|
120
|
+
processing: NullType,
|
|
48
121
|
});
|
|
49
122
|
//# sourceMappingURL=transfer.js.map
|
package/dist/src/transfer.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transfer.js","sourceRoot":"","sources":["../../src/transfer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH
|
|
1
|
+
{"version":3,"file":"transfer.js","sourceRoot":"","sources":["../../src/transfer.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAoB,MAAM,eAAe,CAAC;AAEvH;;;;;;;;;GASG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC;AAE3C;;;;;;GAMG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAqB,EAAE,SAA0B;IACjF,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;IAC3B,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAC/B,OAAO,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,GAAG,IAAI,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,CAAC;AAChE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAqB,EACrB,SAA0B,EAC1B,IAAY;IAEZ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,IAAI,GAAG,iBAAiB,CAAC,IAAI,EAAE,SAAS,CAAC;QAAE,OAAO,IAAI,CAAC;IAClG,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IACnD,MAAM,GAAG,GAAG,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAChG,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;AACpD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,UAAU,CAAC;IAClD,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,WAAW;CAClB,CAAC,CAAC;AAGH;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,MAAM,0BAA0B,GAAG,WAAW,CAAC;IACpD,SAAS,EAAE,QAAQ;IACnB,MAAM,EAAE,UAAU,CAAC;QACjB,EAAE,EAAE,UAAU;QACd,SAAS,EAAE,UAAU;KACtB,CAAC;IACF,YAAY,EAAE,UAAU,CAAC;QACvB,EAAE,EAAE,UAAU;QACd,SAAS,EAAE,WAAW;KACvB,CAAC;CACH,CAAC,CAAC;AAGH;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,UAAU,CAAC;IACjD,GAAG,EAAE,UAAU;IACf,OAAO,EAAE,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;CAC1C,CAAC,CAAC;AAGH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,WAAW,CAAC;IAClD,SAAS,EAAE,QAAQ;IACnB,KAAK,EAAE,UAAU,CAAC;QAChB,OAAO,EAAE,UAAU;KACpB,CAAC;IACF,UAAU,EAAE,QAAQ;CACrB,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transfer.spec.d.ts","sourceRoot":"","sources":["../../src/transfer.spec.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|
|
@@ -0,0 +1,75 @@
|
|
|
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
|
+
* The dataset transfer protocol's two invariants that live in e3-types.
|
|
7
|
+
*
|
|
8
|
+
* Part arithmetic is shared by the client that cuts the bytes and the server
|
|
9
|
+
* that places them, so an off-by-one on either side would corrupt uploads
|
|
10
|
+
* silently until the commit's hash check. And protocol 2 only adds variant
|
|
11
|
+
* cases: a version-1 client decodes a version-1 answer from a new server, and a
|
|
12
|
+
* new client decodes an old server's answers, only while every added case sorts
|
|
13
|
+
* after the version-1 cases and so leaves their tags unchanged.
|
|
14
|
+
*/
|
|
15
|
+
import { describe, it } from 'node:test';
|
|
16
|
+
import * as assert from 'node:assert';
|
|
17
|
+
import { NullType, StringType, StructType, VariantType, decodeBeast2For, encodeBeast2For, variant } from '@elaraai/east';
|
|
18
|
+
import { TransferDoneResponseType, TransferUploadResponseType, transferPartCount, transferPartRange, } from './transfer.js';
|
|
19
|
+
describe('transferPartCount / transferPartRange', () => {
|
|
20
|
+
it('cuts an upload into full parts and a remainder', () => {
|
|
21
|
+
assert.strictEqual(transferPartCount(10, 4), 3);
|
|
22
|
+
assert.deepStrictEqual(transferPartRange(10, 4, 1), { start: 0, end: 4 });
|
|
23
|
+
assert.deepStrictEqual(transferPartRange(10, 4, 2), { start: 4, end: 8 });
|
|
24
|
+
assert.deepStrictEqual(transferPartRange(10, 4, 3), { start: 8, end: 10 });
|
|
25
|
+
});
|
|
26
|
+
it('gives an exact multiple no empty trailing part', () => {
|
|
27
|
+
assert.strictEqual(transferPartCount(8, 4), 2);
|
|
28
|
+
assert.deepStrictEqual(transferPartRange(8, 4, 2), { start: 4, end: 8 });
|
|
29
|
+
assert.strictEqual(transferPartRange(8, 4, 3), null);
|
|
30
|
+
});
|
|
31
|
+
it('sends an upload no larger than a part as one part', () => {
|
|
32
|
+
assert.strictEqual(transferPartCount(3, 4), 1);
|
|
33
|
+
assert.strictEqual(transferPartCount(4, 4), 1);
|
|
34
|
+
assert.deepStrictEqual(transferPartRange(3, 4, 1), { start: 0, end: 3 });
|
|
35
|
+
});
|
|
36
|
+
it('has no part 0, no fractional part and none past the end', () => {
|
|
37
|
+
assert.strictEqual(transferPartRange(10, 4, 0), null);
|
|
38
|
+
assert.strictEqual(transferPartRange(10, 4, 1.5), null);
|
|
39
|
+
assert.strictEqual(transferPartRange(10, 4, 4), null);
|
|
40
|
+
});
|
|
41
|
+
it('stays exact past 2^32 bytes, taking sizes as bigint or number', () => {
|
|
42
|
+
const size = 5n * 1024n ** 4n + 1n; // 5 TiB and a byte
|
|
43
|
+
const partBytes = 512n * 1024n ** 2n;
|
|
44
|
+
assert.strictEqual(transferPartCount(size, partBytes), 10241);
|
|
45
|
+
assert.deepStrictEqual(transferPartRange(Number(size), Number(partBytes), 10241), {
|
|
46
|
+
start: Number(size) - 1,
|
|
47
|
+
end: Number(size),
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
describe('transfer wire compatibility across protocol versions', () => {
|
|
52
|
+
// The version-1 shapes, as a client and server built before protocol 2 hold them.
|
|
53
|
+
const UploadResponseV1 = VariantType({
|
|
54
|
+
completed: NullType,
|
|
55
|
+
upload: StructType({ id: StringType, uploadUrl: StringType }),
|
|
56
|
+
});
|
|
57
|
+
const DoneResponseV1 = VariantType({
|
|
58
|
+
completed: NullType,
|
|
59
|
+
error: StructType({ message: StringType }),
|
|
60
|
+
});
|
|
61
|
+
it('keeps the init answers a version-1 client decodes', () => {
|
|
62
|
+
const upload = variant('upload', { id: 'abc', uploadUrl: 'http://h/api/uploads/abc' });
|
|
63
|
+
assert.deepStrictEqual(decodeBeast2For(UploadResponseV1)(encodeBeast2For(TransferUploadResponseType)(upload)), upload);
|
|
64
|
+
assert.deepStrictEqual(decodeBeast2For(TransferUploadResponseType)(encodeBeast2For(UploadResponseV1)(upload)), upload);
|
|
65
|
+
const completed = variant('completed', null);
|
|
66
|
+
assert.deepStrictEqual(decodeBeast2For(UploadResponseV1)(encodeBeast2For(TransferUploadResponseType)(completed)), completed);
|
|
67
|
+
});
|
|
68
|
+
it('keeps the commit answers a version-1 client decodes', () => {
|
|
69
|
+
for (const done of [variant('completed', null), variant('error', { message: 'hash mismatch' })]) {
|
|
70
|
+
assert.deepStrictEqual(decodeBeast2For(DoneResponseV1)(encodeBeast2For(TransferDoneResponseType)(done)), done);
|
|
71
|
+
assert.deepStrictEqual(decodeBeast2For(TransferDoneResponseType)(encodeBeast2For(DoneResponseV1)(done)), done);
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
//# sourceMappingURL=transfer.spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transfer.spec.js","sourceRoot":"","sources":["../../src/transfer.spec.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elaraai/e3-types",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.78",
|
|
4
4
|
"description": "Shared type definitions for e3 (East Execution Engine)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"directory": "libs/e3/packages/e3-types"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
|
-
"@elaraai/east": "1.0.
|
|
21
|
+
"@elaraai/east": "1.0.78"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^22.0.0",
|
package/src/api.ts
CHANGED
|
@@ -69,6 +69,15 @@ export const DataflowErrorType = StructType({ message: StringType });
|
|
|
69
69
|
export const PermissionDeniedErrorType = StructType({ path: StringType });
|
|
70
70
|
export const InternalErrorType = StructType({ message: StringType });
|
|
71
71
|
export const RepositoryNotFoundErrorType = StructType({ repo: StringType });
|
|
72
|
+
/** A write refused at the door because the bytes' wire type is not the type the
|
|
73
|
+
* dataset declares. `message` is the shared one-line rendering (declared type,
|
|
74
|
+
* given type, first differing field) so a remote `e3 dataset set` prints the
|
|
75
|
+
* line a local one does. */
|
|
76
|
+
export const DatasetTypeMismatchErrorType = StructType({
|
|
77
|
+
workspace: StringType,
|
|
78
|
+
path: StringType,
|
|
79
|
+
message: StringType,
|
|
80
|
+
});
|
|
72
81
|
|
|
73
82
|
export const ErrorType = VariantType({
|
|
74
83
|
repository_not_found: RepositoryNotFoundErrorType,
|
|
@@ -87,6 +96,9 @@ export const ErrorType = VariantType({
|
|
|
87
96
|
dataflow_aborted: NullType,
|
|
88
97
|
permission_denied: PermissionDeniedErrorType,
|
|
89
98
|
internal: InternalErrorType,
|
|
99
|
+
// Appended last: BEAST2 encodes a variant case by index, so a case added at
|
|
100
|
+
// the end leaves every existing payload decodable.
|
|
101
|
+
dataset_type_mismatch: DatasetTypeMismatchErrorType,
|
|
90
102
|
});
|
|
91
103
|
|
|
92
104
|
// =============================================================================
|
|
@@ -725,6 +737,12 @@ export const DatasetStatusDetailType = StructType({
|
|
|
725
737
|
refType: StringType,
|
|
726
738
|
hash: OptionType(StringType),
|
|
727
739
|
size: OptionType(IntegerType),
|
|
740
|
+
/** Segment and element counts of a stored collection, read from the blob's
|
|
741
|
+
* trailing index — so a re-pointed input is inspectable without decoding
|
|
742
|
+
* it. `none` for a non-collection or an unset dataset. Appended LAST, per
|
|
743
|
+
* the positional struct rule. */
|
|
744
|
+
segments: OptionType(IntegerType),
|
|
745
|
+
rows: OptionType(IntegerType),
|
|
728
746
|
});
|
|
729
747
|
|
|
730
748
|
// =============================================================================
|
package/src/dataflow.ts
CHANGED
|
@@ -261,19 +261,23 @@ export type ExecutionEvent = ValueTypeOf<typeof ExecutionEventType>;
|
|
|
261
261
|
|
|
262
262
|
/**
|
|
263
263
|
* Progress notification for one unit of a partitioned task — a partition
|
|
264
|
-
* slice execution or a
|
|
265
|
-
* callbacks while the logical task runs. Deliberately NOT
|
|
266
|
-
* execution events: {@link ExecutionEventType} is a frozen beast2
|
|
267
|
-
* its wire warning), and nothing consumes persisted partition
|
|
268
|
-
* local `[PART]`/`[MERGE]` lines come straight from
|
|
264
|
+
* slice execution, a combine step or a merge unit. Delivered through
|
|
265
|
+
* runner-layer callbacks while the logical task runs. Deliberately NOT
|
|
266
|
+
* persisted as execution events: {@link ExecutionEventType} is a frozen beast2
|
|
267
|
+
* wire (see its wire warning), and nothing consumes persisted partition
|
|
268
|
+
* progress — local `[PART]`/`[MERGE]`/`[COMBINE]` lines come straight from
|
|
269
|
+
* this callback.
|
|
269
270
|
*/
|
|
270
271
|
export interface PartitionProgress {
|
|
271
272
|
/** Which phase the unit belongs to. */
|
|
272
|
-
phase: 'partition' | 'combine';
|
|
273
|
+
phase: 'partition' | 'combine' | 'merge';
|
|
273
274
|
/** Zero-based index of the unit within its phase. */
|
|
274
275
|
index: number;
|
|
275
276
|
/** Total units in the phase (partitions, or combine steps in the level). */
|
|
276
277
|
total: number;
|
|
278
|
+
/** Units of the phase completed so far, including this one when `state`
|
|
279
|
+
* is `completed`. */
|
|
280
|
+
completed: number;
|
|
277
281
|
/** Whether the unit started or finished. */
|
|
278
282
|
state: 'started' | 'completed';
|
|
279
283
|
/** Whether the unit was served from the execution cache (completed only). */
|
|
@@ -0,0 +1,134 @@
|
|
|
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 ONE check that a value's wire type is the type its dataset declares.
|
|
8
|
+
*
|
|
9
|
+
* No door into a dataset used to compare the two. `workspaceSetDataset`
|
|
10
|
+
* validated the path and the `writable` flag and then encoded with whatever
|
|
11
|
+
* type the caller passed; `e3 dataset set` passed a `.beast2` argument's own
|
|
12
|
+
* header type; the API `PUT` forwarded the body's header type; the transfer
|
|
13
|
+
* commit checked only that a collection blob carried an index. So a delivery
|
|
14
|
+
* whose schema had moved was accepted at the boundary and failed later, inside
|
|
15
|
+
* the consuming task — a decode error on the runner, or `cannot open a blob of
|
|
16
|
+
* type ... as ...` out of `FileSystem.openBeast` — naming neither the dataset
|
|
17
|
+
* that was set nor the field that moved.
|
|
18
|
+
*
|
|
19
|
+
* This lives in `e3-types` for the reason {@link encodeDatasetBlob} does: the
|
|
20
|
+
* rule has to hold at every door, and the doors are spread across `e3` (the
|
|
21
|
+
* export's `file` source check), `e3-core` (the set and the adopt) and
|
|
22
|
+
* `e3-api-server` (the `PUT` and the transfer commit), which share no package
|
|
23
|
+
* above this one.
|
|
24
|
+
*
|
|
25
|
+
* **Equality, not assignability.** A runner decodes the object *by the declared
|
|
26
|
+
* type* and beast2 decoding is type-directed, so a blob that is merely a
|
|
27
|
+
* subtype still decodes wrong. `isTypeValueEqual` is the relation.
|
|
28
|
+
*
|
|
29
|
+
* @packageDocumentation
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import {
|
|
33
|
+
diffTypeValues,
|
|
34
|
+
isTypeValueEqual,
|
|
35
|
+
isVariant,
|
|
36
|
+
printTypeValueSummary,
|
|
37
|
+
renderTypeDiff,
|
|
38
|
+
toEastTypeValue,
|
|
39
|
+
type EastType,
|
|
40
|
+
type EastTypeValue,
|
|
41
|
+
type TypeDiff,
|
|
42
|
+
} from '@elaraai/east';
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* A declared type and a wire type that disagree, localized to the first
|
|
46
|
+
* differing position.
|
|
47
|
+
*/
|
|
48
|
+
export interface DatasetTypeMismatch {
|
|
49
|
+
/** The type the dataset declares. */
|
|
50
|
+
readonly declared: EastTypeValue;
|
|
51
|
+
/** The type the bytes carry. */
|
|
52
|
+
readonly given: EastTypeValue;
|
|
53
|
+
/** Every localized difference — see `diffTypeValues`. */
|
|
54
|
+
readonly diffs: readonly TypeDiff[];
|
|
55
|
+
/** The one-line message every door reports. */
|
|
56
|
+
readonly message: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Normalizes either type representation to the homoiconic value form. */
|
|
60
|
+
function asTypeValue(type: EastType | EastTypeValue): EastTypeValue {
|
|
61
|
+
return isVariant(type) ? (type as EastTypeValue) : toEastTypeValue(type as EastType);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Compares a dataset's declared type with the type some bytes carry.
|
|
66
|
+
*
|
|
67
|
+
* @param subject - what is being written, as it should read at the head of the
|
|
68
|
+
* message — e.g. `dataset '.inputs.table'` or `input 'table'`
|
|
69
|
+
* @param carrier - what carries the given type, e.g. `the value` or a file path
|
|
70
|
+
* @param declared - the type the dataset declares
|
|
71
|
+
* @param given - the type the bytes carry
|
|
72
|
+
* @returns `null` when the types are equal, else the localized mismatch
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* const bad = checkDatasetType("dataset '.inputs.table'", 'the value', declared, header);
|
|
77
|
+
* if (bad) throw new DatasetTypeMismatchError(ws, '.inputs.table', bad);
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export function checkDatasetType(
|
|
81
|
+
subject: string,
|
|
82
|
+
carrier: string,
|
|
83
|
+
declared: EastType | EastTypeValue,
|
|
84
|
+
given: EastType | EastTypeValue,
|
|
85
|
+
): DatasetTypeMismatch | null {
|
|
86
|
+
const declaredValue = asTypeValue(declared);
|
|
87
|
+
const givenValue = asTypeValue(given);
|
|
88
|
+
if (isTypeValueEqual(declaredValue, givenValue)) return null;
|
|
89
|
+
|
|
90
|
+
// Equality failed, so a diff normally localizes it — except where the two are
|
|
91
|
+
// assignable but unequal (a subtype, or the same struct fields in another
|
|
92
|
+
// order), which the assignability-shaped diff prunes to nothing. Say so
|
|
93
|
+
// plainly rather than printing a headline with no location under it.
|
|
94
|
+
const diffs = diffTypeValues(givenValue, declaredValue);
|
|
95
|
+
const first = diffs[0];
|
|
96
|
+
const where = first
|
|
97
|
+
? `first difference at ${renderTypeDiff([first])}`
|
|
98
|
+
: 'the two are assignable but not identical — a dataset is decoded by its declared type, ' +
|
|
99
|
+
`so the bytes must carry exactly it${structuralHint(declaredValue, givenValue)}`;
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
declared: declaredValue,
|
|
103
|
+
given: givenValue,
|
|
104
|
+
diffs,
|
|
105
|
+
message:
|
|
106
|
+
`${subject} declares ${printTypeValueSummary(declaredValue, 2)} but ${carrier} carries ` +
|
|
107
|
+
`${printTypeValueSummary(givenValue, 2)} — ${where}`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Names the commonest assignable-but-unequal case: the same struct fields in a
|
|
112
|
+
* different order (East struct field order is significant on the wire). */
|
|
113
|
+
function structuralHint(declared: EastTypeValue, given: EastTypeValue): string {
|
|
114
|
+
if (declared.type !== 'Struct' || given.type !== 'Struct') return '';
|
|
115
|
+
const declaredOrder = (declared.value as { name: string }[]).map((f) => f.name);
|
|
116
|
+
const givenOrder = (given.value as { name: string }[]).map((f) => f.name);
|
|
117
|
+
if (declaredOrder.length !== givenOrder.length) return '';
|
|
118
|
+
const key = (names: string[]) => names.join('\\');
|
|
119
|
+
if (key([...declaredOrder].sort()) !== key([...givenOrder].sort())) return '';
|
|
120
|
+
if (key(declaredOrder) === key(givenOrder)) return '';
|
|
121
|
+
return ` (same fields, different order: expected ${declaredOrder.join(', ')}; found ${givenOrder.join(', ')})`;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Renders a dataset's tree path as it appears in messages: `.inputs.table`.
|
|
126
|
+
*
|
|
127
|
+
* Exported so every door spells the subject identically.
|
|
128
|
+
*
|
|
129
|
+
* @param segments - the path's field names, root-first
|
|
130
|
+
* @returns the dotted address, or `(root)` for an empty path
|
|
131
|
+
*/
|
|
132
|
+
export function datasetAddress(segments: readonly string[]): string {
|
|
133
|
+
return segments.length === 0 ? '(root)' : segments.map((s) => `.${s}`).join('');
|
|
134
|
+
}
|
package/src/execution.ts
CHANGED
|
@@ -97,3 +97,21 @@ export const ExecutionStatusType = VariantType({
|
|
|
97
97
|
});
|
|
98
98
|
|
|
99
99
|
export type ExecutionStatus = ValueTypeOf<typeof ExecutionStatusType>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* The orchestrator process that launched an execution (issue #770).
|
|
103
|
+
*
|
|
104
|
+
* @remarks
|
|
105
|
+
* Written beside the execution's `running` status as the `owner` sidecar. A
|
|
106
|
+
* `running` record whose runner is gone is repaired only when its owner is
|
|
107
|
+
* gone too: a live owner may be between the runner's exit and the record's
|
|
108
|
+
* write, hashing the output.
|
|
109
|
+
*/
|
|
110
|
+
export interface ExecutionOwner {
|
|
111
|
+
/** Process ID of the orchestrator */
|
|
112
|
+
pid: number;
|
|
113
|
+
/** Orchestrator start time in jiffies since boot (from /proc/<pid>/stat field 22) */
|
|
114
|
+
pidStartTime: number;
|
|
115
|
+
/** System boot ID (from /proc/sys/kernel/random/boot_id) */
|
|
116
|
+
bootId: string;
|
|
117
|
+
}
|