@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.
Files changed (66) hide show
  1. package/dist/src/api.d.ts +25 -0
  2. package/dist/src/api.d.ts.map +1 -1
  3. package/dist/src/api.js +18 -0
  4. package/dist/src/api.js.map +1 -1
  5. package/dist/src/dataflow.d.ts +10 -6
  6. package/dist/src/dataflow.d.ts.map +1 -1
  7. package/dist/src/dataflow.js.map +1 -1
  8. package/dist/src/dataset-type.d.ts +71 -0
  9. package/dist/src/dataset-type.d.ts.map +1 -0
  10. package/dist/src/dataset-type.js +101 -0
  11. package/dist/src/dataset-type.js.map +1 -0
  12. package/dist/src/execution.d.ts +17 -0
  13. package/dist/src/execution.d.ts.map +1 -1
  14. package/dist/src/index.d.ts +8 -6
  15. package/dist/src/index.d.ts.map +1 -1
  16. package/dist/src/index.js +10 -5
  17. package/dist/src/index.js.map +1 -1
  18. package/dist/src/package.d.ts +33 -3
  19. package/dist/src/package.d.ts.map +1 -1
  20. package/dist/src/package.js +47 -8
  21. package/dist/src/package.js.map +1 -1
  22. package/dist/src/runner.d.ts +35 -9
  23. package/dist/src/runner.d.ts.map +1 -1
  24. package/dist/src/runner.js +46 -13
  25. package/dist/src/runner.js.map +1 -1
  26. package/dist/src/runner.spec.js +50 -1
  27. package/dist/src/runner.spec.js.map +1 -1
  28. package/dist/src/stream.d.ts +97 -0
  29. package/dist/src/stream.d.ts.map +1 -0
  30. package/dist/src/stream.js +134 -0
  31. package/dist/src/stream.js.map +1 -0
  32. package/dist/src/stream.spec.d.ts +6 -0
  33. package/dist/src/stream.spec.d.ts.map +1 -0
  34. package/dist/src/stream.spec.js +60 -0
  35. package/dist/src/stream.spec.js.map +1 -0
  36. package/dist/src/task.d.ts +198 -10
  37. package/dist/src/task.d.ts.map +1 -1
  38. package/dist/src/task.js +340 -11
  39. package/dist/src/task.js.map +1 -1
  40. package/dist/src/task.spec.d.ts +6 -0
  41. package/dist/src/task.spec.d.ts.map +1 -0
  42. package/dist/src/task.spec.js +169 -0
  43. package/dist/src/task.spec.js.map +1 -0
  44. package/dist/src/transfer.d.ts +70 -3
  45. package/dist/src/transfer.d.ts.map +1 -1
  46. package/dist/src/transfer.js +76 -3
  47. package/dist/src/transfer.js.map +1 -1
  48. package/dist/src/transfer.spec.d.ts +6 -0
  49. package/dist/src/transfer.spec.d.ts.map +1 -0
  50. package/dist/src/transfer.spec.js +75 -0
  51. package/dist/src/transfer.spec.js.map +1 -0
  52. package/package.json +2 -2
  53. package/src/api.ts +18 -0
  54. package/src/dataflow.ts +10 -6
  55. package/src/dataset-type.ts +134 -0
  56. package/src/execution.ts +18 -0
  57. package/src/index.ts +37 -0
  58. package/src/package.ts +50 -8
  59. package/src/runner.spec.ts +56 -1
  60. package/src/runner.ts +45 -13
  61. package/src/stream.spec.ts +88 -0
  62. package/src/stream.ts +170 -0
  63. package/src/task.spec.ts +208 -0
  64. package/src/task.ts +374 -13
  65. package/src/transfer.spec.ts +98 -0
  66. package/src/transfer.ts +84 -3
@@ -0,0 +1,98 @@
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 dataset transfer protocol's two invariants that live in e3-types.
8
+ *
9
+ * Part arithmetic is shared by the client that cuts the bytes and the server
10
+ * that places them, so an off-by-one on either side would corrupt uploads
11
+ * silently until the commit's hash check. And protocol 2 only adds variant
12
+ * cases: a version-1 client decodes a version-1 answer from a new server, and a
13
+ * new client decodes an old server's answers, only while every added case sorts
14
+ * after the version-1 cases and so leaves their tags unchanged.
15
+ */
16
+
17
+ import { describe, it } from 'node:test';
18
+ import * as assert from 'node:assert';
19
+ import { NullType, StringType, StructType, VariantType, decodeBeast2For, encodeBeast2For, variant } from '@elaraai/east';
20
+ import {
21
+ TransferDoneResponseType,
22
+ TransferUploadResponseType,
23
+ transferPartCount,
24
+ transferPartRange,
25
+ } from './transfer.js';
26
+
27
+ describe('transferPartCount / transferPartRange', () => {
28
+ it('cuts an upload into full parts and a remainder', () => {
29
+ assert.strictEqual(transferPartCount(10, 4), 3);
30
+ assert.deepStrictEqual(transferPartRange(10, 4, 1), { start: 0, end: 4 });
31
+ assert.deepStrictEqual(transferPartRange(10, 4, 2), { start: 4, end: 8 });
32
+ assert.deepStrictEqual(transferPartRange(10, 4, 3), { start: 8, end: 10 });
33
+ });
34
+
35
+ it('gives an exact multiple no empty trailing part', () => {
36
+ assert.strictEqual(transferPartCount(8, 4), 2);
37
+ assert.deepStrictEqual(transferPartRange(8, 4, 2), { start: 4, end: 8 });
38
+ assert.strictEqual(transferPartRange(8, 4, 3), null);
39
+ });
40
+
41
+ it('sends an upload no larger than a part as one part', () => {
42
+ assert.strictEqual(transferPartCount(3, 4), 1);
43
+ assert.strictEqual(transferPartCount(4, 4), 1);
44
+ assert.deepStrictEqual(transferPartRange(3, 4, 1), { start: 0, end: 3 });
45
+ });
46
+
47
+ it('has no part 0, no fractional part and none past the end', () => {
48
+ assert.strictEqual(transferPartRange(10, 4, 0), null);
49
+ assert.strictEqual(transferPartRange(10, 4, 1.5), null);
50
+ assert.strictEqual(transferPartRange(10, 4, 4), null);
51
+ });
52
+
53
+ it('stays exact past 2^32 bytes, taking sizes as bigint or number', () => {
54
+ const size = 5n * 1024n ** 4n + 1n; // 5 TiB and a byte
55
+ const partBytes = 512n * 1024n ** 2n;
56
+ assert.strictEqual(transferPartCount(size, partBytes), 10241);
57
+ assert.deepStrictEqual(transferPartRange(Number(size), Number(partBytes), 10241), {
58
+ start: Number(size) - 1,
59
+ end: Number(size),
60
+ });
61
+ });
62
+ });
63
+
64
+ describe('transfer wire compatibility across protocol versions', () => {
65
+ // The version-1 shapes, as a client and server built before protocol 2 hold them.
66
+ const UploadResponseV1 = VariantType({
67
+ completed: NullType,
68
+ upload: StructType({ id: StringType, uploadUrl: StringType }),
69
+ });
70
+ const DoneResponseV1 = VariantType({
71
+ completed: NullType,
72
+ error: StructType({ message: StringType }),
73
+ });
74
+
75
+ it('keeps the init answers a version-1 client decodes', () => {
76
+ const upload = variant('upload', { id: 'abc', uploadUrl: 'http://h/api/uploads/abc' });
77
+ assert.deepStrictEqual(
78
+ decodeBeast2For(UploadResponseV1)(encodeBeast2For(TransferUploadResponseType)(upload)),
79
+ upload,
80
+ );
81
+ assert.deepStrictEqual(
82
+ decodeBeast2For(TransferUploadResponseType)(encodeBeast2For(UploadResponseV1)(upload)),
83
+ upload,
84
+ );
85
+ const completed = variant('completed', null);
86
+ assert.deepStrictEqual(
87
+ decodeBeast2For(UploadResponseV1)(encodeBeast2For(TransferUploadResponseType)(completed)),
88
+ completed,
89
+ );
90
+ });
91
+
92
+ it('keeps the commit answers a version-1 client decodes', () => {
93
+ for (const done of [variant('completed', null), variant('error', { message: 'hash mismatch' })] as const) {
94
+ assert.deepStrictEqual(decodeBeast2For(DoneResponseV1)(encodeBeast2For(TransferDoneResponseType)(done)), done);
95
+ assert.deepStrictEqual(decodeBeast2For(TransferDoneResponseType)(encodeBeast2For(DoneResponseV1)(done)), done);
96
+ }
97
+ });
98
+ });
package/src/transfer.ts CHANGED
@@ -8,9 +8,60 @@
8
8
  *
9
9
  * Used by both the API server and client for large dataset uploads
10
10
  * that exceed inline body size limits.
11
+ *
12
+ * Two protocol versions share these types. A client names the version it speaks
13
+ * with `?protocol=N` on the init and commit requests (a request without it is
14
+ * version 1), and a server answers only in the forms that version understands —
15
+ * every case version 2 adds sorts after the version-1 cases, so the tags a
16
+ * version-1 client decodes keep their numbers.
17
+ */
18
+
19
+ import { VariantType, StructType, StringType, IntegerType, NullType, DictType, type ValueTypeOf } from '@elaraai/east';
20
+
21
+ /**
22
+ * The dataset transfer protocol version this build speaks.
23
+ *
24
+ * @remarks
25
+ * Version 2 lets the server plan an upload as parts — each sent to its own URL
26
+ * with the request headers the server names, so an object store can bind a
27
+ * checksum to a single PUT or take a multipart upload larger than one PUT
28
+ * allows — and lets a commit answer `processing` while the server verifies the
29
+ * bytes, for the client to poll.
30
+ */
31
+ export const TRANSFER_PROTOCOL_VERSION = 2;
32
+
33
+ /**
34
+ * How many parts an upload of `size` bytes has when planned with `partBytes`.
35
+ *
36
+ * @param size - The upload's size in bytes
37
+ * @param partBytes - The size of every part but the last
38
+ * @returns The part count — at least 1
11
39
  */
40
+ export function transferPartCount(size: number | bigint, partBytes: number | bigint): number {
41
+ const total = BigInt(size);
42
+ const each = BigInt(partBytes);
43
+ return total <= each ? 1 : Number((total + each - 1n) / each);
44
+ }
12
45
 
13
- import { VariantType, StructType, StringType, IntegerType, NullType, type ValueTypeOf } from '@elaraai/east';
46
+ /**
47
+ * The byte range part `part` of an upload covers.
48
+ *
49
+ * @param size - The upload's size in bytes
50
+ * @param partBytes - The size of every part but the last
51
+ * @param part - The part's number, from 1
52
+ * @returns The half-open range `[start, end)`, or `null` when the upload has no
53
+ * such part
54
+ */
55
+ export function transferPartRange(
56
+ size: number | bigint,
57
+ partBytes: number | bigint,
58
+ part: number
59
+ ): { start: number; end: number } | null {
60
+ if (!Number.isInteger(part) || part < 1 || part > transferPartCount(size, partBytes)) return null;
61
+ const start = BigInt(part - 1) * BigInt(partBytes);
62
+ const end = start + BigInt(partBytes) < BigInt(size) ? start + BigInt(partBytes) : BigInt(size);
63
+ return { start: Number(start), end: Number(end) };
64
+ }
14
65
 
15
66
  /**
16
67
  * Transfer upload init request.
@@ -30,7 +81,15 @@ export type TransferUploadRequest = ValueTypeOf<typeof TransferUploadRequestType
30
81
  * Transfer upload init response.
31
82
  *
32
83
  * - `completed`: Object already exists (dedup), dataset ref updated
33
- * - `upload`: Staging slot created, client should upload then call done
84
+ * - `upload`: Staging slot created; the client PUTs every byte to `uploadUrl`,
85
+ * then commits
86
+ * - `upload_parts` (protocol 2): the client sends the bytes as parts of
87
+ * `partBytes` bytes each (the last part holds the remainder, and an upload no
88
+ * larger than `partBytes` is one part). Part `n` (1-based) is the byte range
89
+ * `[(n - 1) * partBytes, min(size, n * partBytes))`; its URL and required
90
+ * headers come from `GET …/upload/<id>/parts/<n>`. Parts may be sent in any
91
+ * order and concurrently, and re-sending a part replaces it. The client
92
+ * commits once every part is sent.
34
93
  */
35
94
  export const TransferUploadResponseType = VariantType({
36
95
  completed: NullType,
@@ -38,19 +97,41 @@ export const TransferUploadResponseType = VariantType({
38
97
  id: StringType,
39
98
  uploadUrl: StringType,
40
99
  }),
100
+ upload_parts: StructType({
101
+ id: StringType,
102
+ partBytes: IntegerType,
103
+ }),
41
104
  });
42
105
  export type TransferUploadResponse = ValueTypeOf<typeof TransferUploadResponseType>;
43
106
 
44
107
  /**
45
- * Transfer done response.
108
+ * Where and how to send one part of a protocol-2 upload.
109
+ *
110
+ * @property url - The URL the client PUTs the part's bytes to, without an
111
+ * `Authorization` header (it may be a presigned object-store URL)
112
+ * @property headers - Request headers the PUT must carry exactly as given — a
113
+ * presigned URL can sign them, e.g. the checksum an object store verifies the
114
+ * bytes against
115
+ */
116
+ export const TransferPartResponseType = StructType({
117
+ url: StringType,
118
+ headers: DictType(StringType, StringType),
119
+ });
120
+ export type TransferPartResponse = ValueTypeOf<typeof TransferPartResponseType>;
121
+
122
+ /**
123
+ * Transfer done response — the commit's answer, and the poll's.
46
124
  *
47
125
  * - `completed`: Hash verified, object stored, dataset ref updated
48
126
  * - `error`: Hash mismatch or other failure
127
+ * - `processing` (protocol 2): the server is still verifying the bytes; poll
128
+ * `GET …/upload/<id>` until it answers `completed` or `error`
49
129
  */
50
130
  export const TransferDoneResponseType = VariantType({
51
131
  completed: NullType,
52
132
  error: StructType({
53
133
  message: StringType,
54
134
  }),
135
+ processing: NullType,
55
136
  });
56
137
  export type TransferDoneResponse = ValueTypeOf<typeof TransferDoneResponseType>;