@elaraai/e3-types 1.0.79 → 1.0.80
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/README.md +1 -1
- package/dist/src/api.d.ts +569 -36
- package/dist/src/api.d.ts.map +1 -1
- package/dist/src/api.js +257 -32
- package/dist/src/api.js.map +1 -1
- package/dist/src/collection-manifest.d.ts +37 -0
- package/dist/src/collection-manifest.d.ts.map +1 -0
- package/dist/src/collection-manifest.js +37 -0
- package/dist/src/collection-manifest.js.map +1 -0
- package/dist/src/dataflow.d.ts +264 -19
- package/dist/src/dataflow.d.ts.map +1 -1
- package/dist/src/dataflow.js +156 -15
- package/dist/src/dataflow.js.map +1 -1
- package/dist/src/dataflow.spec.js +184 -27
- package/dist/src/dataflow.spec.js.map +1 -1
- package/dist/src/dataset-blob.d.ts +75 -15
- package/dist/src/dataset-blob.d.ts.map +1 -1
- package/dist/src/dataset-blob.js +95 -28
- package/dist/src/dataset-blob.js.map +1 -1
- package/dist/src/dataset-blob.spec.d.ts +6 -0
- package/dist/src/dataset-blob.spec.d.ts.map +1 -0
- package/dist/src/dataset-blob.spec.js +92 -0
- package/dist/src/dataset-blob.spec.js.map +1 -0
- package/dist/src/dataset-ref.d.ts +5 -4
- package/dist/src/dataset-ref.d.ts.map +1 -1
- package/dist/src/dataset-ref.js +5 -4
- package/dist/src/dataset-ref.js.map +1 -1
- package/dist/src/environment.d.ts +3 -2
- package/dist/src/environment.d.ts.map +1 -1
- package/dist/src/environment.js +3 -2
- package/dist/src/environment.js.map +1 -1
- package/dist/src/execution.d.ts +112 -18
- package/dist/src/execution.d.ts.map +1 -1
- package/dist/src/execution.js +174 -45
- package/dist/src/execution.js.map +1 -1
- package/dist/src/execution.spec.d.ts +6 -0
- package/dist/src/execution.spec.d.ts.map +1 -0
- package/dist/src/execution.spec.js +108 -0
- package/dist/src/execution.spec.js.map +1 -0
- package/dist/src/function.d.ts +14 -12
- package/dist/src/function.d.ts.map +1 -1
- package/dist/src/function.js +17 -32
- package/dist/src/function.js.map +1 -1
- package/dist/src/index.d.ts +16 -12
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +25 -20
- package/dist/src/index.js.map +1 -1
- package/dist/src/lock.d.ts +68 -22
- package/dist/src/lock.d.ts.map +1 -1
- package/dist/src/lock.js +33 -22
- package/dist/src/lock.js.map +1 -1
- package/dist/src/names.d.ts +21 -0
- package/dist/src/names.d.ts.map +1 -0
- package/dist/src/names.js +31 -0
- package/dist/src/names.js.map +1 -0
- package/dist/src/package.d.ts +11 -78
- package/dist/src/package.d.ts.map +1 -1
- package/dist/src/package.js +13 -66
- package/dist/src/package.js.map +1 -1
- package/dist/src/record.d.ts +582 -18
- package/dist/src/record.d.ts.map +1 -1
- package/dist/src/record.js +405 -7
- package/dist/src/record.js.map +1 -1
- package/dist/src/{stream.spec.d.ts → record.spec.d.ts} +1 -1
- package/dist/src/record.spec.d.ts.map +1 -0
- package/dist/src/record.spec.js +143 -0
- package/dist/src/record.spec.js.map +1 -0
- package/dist/src/release.d.ts +36 -0
- package/dist/src/release.d.ts.map +1 -0
- package/dist/src/release.js +71 -0
- package/dist/src/release.js.map +1 -0
- package/dist/src/release.spec.d.ts +6 -0
- package/dist/src/release.spec.d.ts.map +1 -0
- package/dist/src/release.spec.js +42 -0
- package/dist/src/release.spec.js.map +1 -0
- package/dist/src/repository.d.ts +64 -0
- package/dist/src/repository.d.ts.map +1 -0
- package/dist/src/repository.js +49 -0
- package/dist/src/repository.js.map +1 -0
- package/dist/src/runner.d.ts +16 -62
- package/dist/src/runner.d.ts.map +1 -1
- package/dist/src/runner.js +16 -79
- package/dist/src/runner.js.map +1 -1
- package/dist/src/runner.spec.js +11 -85
- package/dist/src/runner.spec.js.map +1 -1
- package/dist/src/structure.d.ts +0 -57
- package/dist/src/structure.d.ts.map +1 -1
- package/dist/src/structure.js +0 -3
- package/dist/src/structure.js.map +1 -1
- package/dist/src/task.d.ts +210 -318
- package/dist/src/task.d.ts.map +1 -1
- package/dist/src/task.js +117 -472
- package/dist/src/task.js.map +1 -1
- package/dist/src/task.spec.js +62 -154
- package/dist/src/task.spec.js.map +1 -1
- package/dist/src/transfer.d.ts +16 -22
- package/dist/src/transfer.d.ts.map +1 -1
- package/dist/src/transfer.js +16 -22
- package/dist/src/transfer.js.map +1 -1
- package/dist/src/transfer.spec.js +4 -34
- package/dist/src/transfer.spec.js.map +1 -1
- package/dist/src/unit-plan.d.ts +130 -0
- package/dist/src/unit-plan.d.ts.map +1 -0
- package/dist/src/unit-plan.js +107 -0
- package/dist/src/unit-plan.js.map +1 -0
- package/dist/src/unit-plan.spec.d.ts +6 -0
- package/dist/src/unit-plan.spec.d.ts.map +1 -0
- package/dist/src/unit-plan.spec.js +29 -0
- package/dist/src/unit-plan.spec.js.map +1 -0
- package/dist/src/workspace.d.ts +22 -11
- package/dist/src/workspace.d.ts.map +1 -1
- package/dist/src/workspace.js +10 -11
- package/dist/src/workspace.js.map +1 -1
- package/package.json +3 -3
- package/src/api.ts +276 -32
- package/src/collection-manifest.ts +53 -0
- package/src/dataflow.spec.ts +223 -31
- package/src/dataflow.ts +183 -26
- package/src/dataset-blob.spec.ts +117 -0
- package/src/dataset-blob.ts +157 -21
- package/src/dataset-ref.ts +5 -4
- package/src/environment.ts +3 -2
- package/src/execution.spec.ts +114 -0
- package/src/execution.ts +175 -51
- package/src/function.ts +19 -32
- package/src/index.ts +122 -39
- package/src/lock.ts +39 -22
- package/src/names.ts +39 -0
- package/src/package.ts +15 -71
- package/src/record.spec.ts +195 -0
- package/src/record.ts +489 -7
- package/src/release.spec.ts +47 -0
- package/src/release.ts +69 -0
- package/src/repository.ts +67 -0
- package/src/runner.spec.ts +11 -94
- package/src/runner.ts +16 -80
- package/src/structure.ts +0 -8
- package/src/task.spec.ts +67 -188
- package/src/task.ts +140 -522
- package/src/transfer.spec.ts +4 -51
- package/src/transfer.ts +16 -22
- package/src/unit-plan.spec.ts +31 -0
- package/src/unit-plan.ts +121 -0
- package/src/workspace.ts +13 -11
- package/dist/src/stream.d.ts +0 -97
- package/dist/src/stream.d.ts.map +0 -1
- package/dist/src/stream.js +0 -134
- package/dist/src/stream.js.map +0 -1
- package/dist/src/stream.spec.d.ts.map +0 -1
- package/dist/src/stream.spec.js +0 -60
- package/dist/src/stream.spec.js.map +0 -1
- package/src/stream.spec.ts +0 -88
- package/src/stream.ts +0 -170
package/src/lock.ts
CHANGED
|
@@ -6,18 +6,15 @@
|
|
|
6
6
|
/**
|
|
7
7
|
* Lock state type definitions.
|
|
8
8
|
*
|
|
9
|
-
* A lock
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* -
|
|
13
|
-
* -
|
|
9
|
+
* A lock gives exclusive or shared access to a resource: a workspace, a
|
|
10
|
+
* workspace's dataflow, the repository's tasks, or one dataset's ref. The state
|
|
11
|
+
* records:
|
|
12
|
+
* - what operation acquired the lock (dataflow, deployment, etc.);
|
|
13
|
+
* - who holds it: a local process, or a cloud function;
|
|
14
|
+
* - when it was acquired;
|
|
15
|
+
* - when it expires, for a cloud backend's lease.
|
|
14
16
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* The holder field is an East text-encoded string, allowing different backends
|
|
18
|
-
* to encode their own holder identification. Common patterns:
|
|
19
|
-
* - Local: `.process (pid=1234, bootId="abc-123", startTime=98765, command="e3 start")`
|
|
20
|
-
* - Cloud: `.lambda (requestId="req-123", functionName="e3-api")`
|
|
17
|
+
* A local repository keeps each resource's locks in `locks/<resource>/`.
|
|
21
18
|
*/
|
|
22
19
|
|
|
23
20
|
import {
|
|
@@ -50,8 +47,8 @@ export const LockOperationType = VariantType({
|
|
|
50
47
|
export type LockOperation = ValueTypeOf<typeof LockOperationType>;
|
|
51
48
|
|
|
52
49
|
/**
|
|
53
|
-
*
|
|
54
|
-
*
|
|
50
|
+
* A local process holding a lock: alive while a process of this pid and start
|
|
51
|
+
* time runs under this boot.
|
|
55
52
|
*/
|
|
56
53
|
export const ProcessHolderType = StructType({
|
|
57
54
|
/** Process ID */
|
|
@@ -67,20 +64,40 @@ export const ProcessHolderType = StructType({
|
|
|
67
64
|
export type ProcessHolder = ValueTypeOf<typeof ProcessHolderType>;
|
|
68
65
|
|
|
69
66
|
/**
|
|
70
|
-
*
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
* A cloud function holding a lock, which the lock's lease bounds.
|
|
68
|
+
*/
|
|
69
|
+
export const LambdaHolderType = StructType({
|
|
70
|
+
/** The invocation's request ID */
|
|
71
|
+
requestId: StringType,
|
|
72
|
+
/** The function's name */
|
|
73
|
+
functionName: StringType,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
export type LambdaHolder = ValueTypeOf<typeof LambdaHolderType>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Who holds a lock.
|
|
80
|
+
*/
|
|
81
|
+
export const LockHolderVariantType = VariantType({
|
|
82
|
+
/** A local process */
|
|
83
|
+
process: ProcessHolderType,
|
|
84
|
+
/** A cloud function */
|
|
85
|
+
lambda: LambdaHolderType,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
export type LockHolderVariant = ValueTypeOf<typeof LockHolderVariantType>;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* The state of a held lock.
|
|
75
92
|
*
|
|
76
|
-
* The
|
|
77
|
-
*
|
|
93
|
+
* The locking itself is the backend's — an atomically created file locally, a
|
|
94
|
+
* conditional write in the cloud — and every backend records this state.
|
|
78
95
|
*/
|
|
79
96
|
export const LockStateType = StructType({
|
|
80
97
|
/** What operation acquired the lock */
|
|
81
98
|
operation: LockOperationType,
|
|
82
|
-
/** Who holds the lock
|
|
83
|
-
holder:
|
|
99
|
+
/** Who holds the lock */
|
|
100
|
+
holder: LockHolderVariantType,
|
|
84
101
|
/** When the lock was acquired */
|
|
85
102
|
acquiredAt: DateTimeType,
|
|
86
103
|
/** When the lock expires (for cloud TTL-based locks) */
|
package/src/names.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
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 rule for the names e3 makes paths of: a repository's name where a server
|
|
8
|
+
* keeps several, a workspace's name, a package's name and version, and a
|
|
9
|
+
* lock's resource. One place holds it, so the SDK, the CLI, the API server and
|
|
10
|
+
* the stores refuse the same names.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/** What a name names. */
|
|
14
|
+
export type NamedKind = 'repository' | 'workspace' | 'package' | 'package version' | 'lock';
|
|
15
|
+
|
|
16
|
+
/** What no name may hold: a path separator, a character a Windows file name
|
|
17
|
+
* refuses, or a control character. */
|
|
18
|
+
const REFUSED = /[/\\:*?"<>|\u0000-\u001f]/;
|
|
19
|
+
|
|
20
|
+
/** What a workspace's name may not hold besides: the characters that join the
|
|
21
|
+
* parts of its locks' names. */
|
|
22
|
+
const REFUSED_IN_WORKSPACE = /[#~]/;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Why a name cannot be one path segment, or `null` when it can.
|
|
26
|
+
*
|
|
27
|
+
* @param kind - What the name names
|
|
28
|
+
* @param name - The name
|
|
29
|
+
* @returns The problem, in words that follow the name, or `null`
|
|
30
|
+
*/
|
|
31
|
+
export function nameProblem(kind: NamedKind, name: string): string | null {
|
|
32
|
+
if (name === '') return 'is empty';
|
|
33
|
+
if (name === '.' || name === '..') return 'is a path of its own';
|
|
34
|
+
const refused = REFUSED.exec(name);
|
|
35
|
+
if (refused !== null) return `holds ${JSON.stringify(refused[0])}, which a file name cannot`;
|
|
36
|
+
const joiner = kind === 'workspace' ? REFUSED_IN_WORKSPACE.exec(name) : null;
|
|
37
|
+
if (joiner !== null) return `holds ${JSON.stringify(joiner[0])}, which joins the parts of a lock's name`;
|
|
38
|
+
return null;
|
|
39
|
+
}
|
package/src/package.ts
CHANGED
|
@@ -39,14 +39,6 @@ export type PackageDataType = typeof PackageDataType;
|
|
|
39
39
|
|
|
40
40
|
export type PackageData = ValueTypeOf<typeof PackageDataType>;
|
|
41
41
|
|
|
42
|
-
// Backwards compatibility alias
|
|
43
|
-
/** @deprecated Use PackageDataType instead */
|
|
44
|
-
export const PackageDatasetsType = PackageDataType;
|
|
45
|
-
/** @deprecated Use PackageData instead */
|
|
46
|
-
export type PackageDatasetsType = PackageDataType;
|
|
47
|
-
/** @deprecated Use PackageData instead */
|
|
48
|
-
export type PackageDatasets = PackageData;
|
|
49
|
-
|
|
50
42
|
/**
|
|
51
43
|
* Package object stored in the object store.
|
|
52
44
|
*
|
|
@@ -100,85 +92,37 @@ export const PackageObjectType = StructType({
|
|
|
100
92
|
data: PackageDataType,
|
|
101
93
|
/** Functions defined in this package: name -> FunctionObject hash. */
|
|
102
94
|
functions: DictType(StringType, StringType),
|
|
103
|
-
/** Records defined in this package: name -> RecordObject hash.
|
|
104
|
-
* BEAST2 encodes struct fields positionally in declaration order, so new
|
|
105
|
-
* fields MUST be appended LAST — never inserted between existing fields. */
|
|
95
|
+
/** Records defined in this package: name -> RecordObject hash. */
|
|
106
96
|
records: DictType(StringType, StringType),
|
|
107
|
-
/** Unresolved dataset sources: refPath (e.g. "inputs/table") -> descriptor.
|
|
108
|
-
* Appended LAST, per the positional rule above. */
|
|
97
|
+
/** Unresolved dataset sources: refPath (e.g. "inputs/table") -> descriptor. */
|
|
109
98
|
sources: DictType(StringType, DatasetSourceWireType),
|
|
110
99
|
});
|
|
111
100
|
export type PackageObjectType = typeof PackageObjectType;
|
|
112
101
|
|
|
113
102
|
export type PackageObject = ValueTypeOf<typeof PackageObjectType>;
|
|
114
103
|
|
|
115
|
-
/**
|
|
116
|
-
* The pre-`sources` package object wire shape (tasks, data, functions,
|
|
117
|
-
* records), kept only so {@link decodePackageObject} can read packages exported
|
|
118
|
-
* before path-initialised inputs existed.
|
|
119
|
-
*/
|
|
120
|
-
const RecordsEraPackageObjectType = StructType({
|
|
121
|
-
tasks: DictType(StringType, StringType),
|
|
122
|
-
data: PackageDataType,
|
|
123
|
-
functions: DictType(StringType, StringType),
|
|
124
|
-
records: DictType(StringType, StringType),
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* The pre-`records` package object wire shape (tasks, data, functions), kept
|
|
129
|
-
* only so {@link decodePackageObject} can read packages exported before records
|
|
130
|
-
* existed.
|
|
131
|
-
*/
|
|
132
|
-
const FunctionsEraPackageObjectType = StructType({
|
|
133
|
-
tasks: DictType(StringType, StringType),
|
|
134
|
-
data: PackageDataType,
|
|
135
|
-
functions: DictType(StringType, StringType),
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* The pre-`functions` package object wire shape (tasks, data), kept only so
|
|
140
|
-
* {@link decodePackageObject} can read packages exported before functions
|
|
141
|
-
* existed.
|
|
142
|
-
*/
|
|
143
|
-
const LegacyPackageObjectType = StructType({
|
|
144
|
-
tasks: DictType(StringType, StringType),
|
|
145
|
-
data: PackageDataType,
|
|
146
|
-
});
|
|
147
|
-
|
|
148
104
|
const decodeCurrent = decodeBeast2For(PackageObjectType);
|
|
149
|
-
const decodeRecordsEra = decodeBeast2For(RecordsEraPackageObjectType);
|
|
150
|
-
const decodeFunctionsEra = decodeBeast2For(FunctionsEraPackageObjectType);
|
|
151
|
-
const decodeLegacy = decodeBeast2For(LegacyPackageObjectType);
|
|
152
105
|
|
|
153
106
|
/**
|
|
154
|
-
* Decode a `PackageObject` from BEAST2 bytes
|
|
155
|
-
*
|
|
107
|
+
* Decode a `PackageObject` from BEAST2 bytes.
|
|
108
|
+
*
|
|
109
|
+
* @remarks
|
|
110
|
+
* A package-borne wire, so it changes by hard cutover: a package exported by
|
|
111
|
+
* an older SDK is re-exported with the current one, and this says so.
|
|
156
112
|
*
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
-
* the
|
|
160
|
-
*
|
|
113
|
+
* @param data - the stored bytes
|
|
114
|
+
* @returns the package object
|
|
115
|
+
* @throws {Error} When the bytes are not a current package object — a package
|
|
116
|
+
* exported by an older SDK, which is re-exported with the current one.
|
|
161
117
|
*/
|
|
162
118
|
export function decodePackageObject(data: Uint8Array): PackageObject {
|
|
163
119
|
try {
|
|
164
120
|
return decodeCurrent(data);
|
|
165
121
|
} catch (err) {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
try {
|
|
171
|
-
const fnEra = decodeFunctionsEra(data);
|
|
172
|
-
return { tasks: fnEra.tasks, data: fnEra.data, functions: fnEra.functions, records: new Map(), sources: new Map() };
|
|
173
|
-
} catch {
|
|
174
|
-
try {
|
|
175
|
-
const legacy = decodeLegacy(data);
|
|
176
|
-
return { tasks: legacy.tasks, data: legacy.data, functions: new Map(), records: new Map(), sources: new Map() };
|
|
177
|
-
} catch {
|
|
178
|
-
throw err; // no known shape — surface the current-format error
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
}
|
|
122
|
+
throw new Error(
|
|
123
|
+
`the package object does not decode: the package was exported by an older e3 SDK — re-export it with the current one ` +
|
|
124
|
+
`(${err instanceof Error ? err.message : String(err)})`,
|
|
125
|
+
);
|
|
182
126
|
}
|
|
183
127
|
}
|
|
184
128
|
|
|
@@ -0,0 +1,195 @@
|
|
|
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 record wire types' decoders: each reads the current shape, and refuses
|
|
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.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { describe, it } from 'node:test';
|
|
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';
|
|
16
|
+
import { RunnerType } from './runner.js';
|
|
17
|
+
import {
|
|
18
|
+
MigrationObjectType,
|
|
19
|
+
MutationObjectType,
|
|
20
|
+
RecordCommitType,
|
|
21
|
+
RecordObjectType,
|
|
22
|
+
decodeMigrationObject,
|
|
23
|
+
decodeMutationObject,
|
|
24
|
+
decodeRecordCommit,
|
|
25
|
+
decodeRecordObject,
|
|
26
|
+
type MigrationObject,
|
|
27
|
+
type MutationObject,
|
|
28
|
+
type RecordCommit,
|
|
29
|
+
type RecordObject,
|
|
30
|
+
} from './record.js';
|
|
31
|
+
|
|
32
|
+
describe('decodeRecordCommit', () => {
|
|
33
|
+
const commit: RecordCommit = {
|
|
34
|
+
parent: some('a'.repeat(64)),
|
|
35
|
+
state: 'b'.repeat(64),
|
|
36
|
+
mutation: 'increment',
|
|
37
|
+
args: some('c'.repeat(64)),
|
|
38
|
+
actor: 'cli:test',
|
|
39
|
+
at: new Date(0),
|
|
40
|
+
delta: some('d'.repeat(64)),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
it('round-trips a current commit', () => {
|
|
44
|
+
assert.deepEqual(decodeRecordCommit(encodeBeast2For(RecordCommitType)(commit)), commit);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('refuses a commit an older e3 wrote before deltas existed, naming the fix', () => {
|
|
48
|
+
const PreDeltaCommitType = StructType({
|
|
49
|
+
parent: OptionType(StringType), state: StringType, mutation: StringType,
|
|
50
|
+
args: OptionType(StringType), actor: StringType, at: DateTimeType,
|
|
51
|
+
});
|
|
52
|
+
const { delta: _delta, ...older } = commit;
|
|
53
|
+
assert.throws(
|
|
54
|
+
() => decodeRecordCommit(encodeBeast2For(PreDeltaCommitType)(older)),
|
|
55
|
+
/^Error: the record commit does not decode: an older e3 wrote this repository — re-create it: deploy again and import its data again \(/,
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('refuses bytes of no commit shape', () => {
|
|
60
|
+
assert.throws(() => decodeRecordCommit(encodeBeast2For(IntegerType)(7n)), /the record commit does not decode/);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('decodeMutationObject', () => {
|
|
65
|
+
const mutation: MutationObject = {
|
|
66
|
+
bodyIr: 'a'.repeat(64),
|
|
67
|
+
argTypes: [toEastTypeValue(IntegerType)],
|
|
68
|
+
runner: variant('east_node', { platforms: ['@elaraai/east-node-std'] }),
|
|
69
|
+
form: variant('edit', null),
|
|
70
|
+
programIr: 'b'.repeat(64),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/** A decoded mutation with its arg types checked off separately: a decoded
|
|
74
|
+
* type value carries no memoized identity, so only the East comparison
|
|
75
|
+
* says whether two of them are the same type. */
|
|
76
|
+
function read(bytes: Uint8Array): Omit<MutationObject, 'argTypes'> {
|
|
77
|
+
const decoded = decodeMutationObject(bytes);
|
|
78
|
+
assert.equal(decoded.argTypes.length, 1);
|
|
79
|
+
assert.ok(isTypeValueEqual(decoded.argTypes[0]!, toEastTypeValue(IntegerType)));
|
|
80
|
+
const { argTypes: _argTypes, ...rest } = decoded;
|
|
81
|
+
return rest;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
it('round-trips a current mutation', () => {
|
|
85
|
+
const { argTypes: _argTypes, ...expected } = mutation;
|
|
86
|
+
assert.deepEqual(read(encodeBeast2For(MutationObjectType)(mutation)), expected);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('refuses a mutation an older SDK exported before the delta, naming the fix', () => {
|
|
90
|
+
const PreDeltaMutationType = StructType({
|
|
91
|
+
bodyIr: StringType, argTypes: ArrayType(EastTypeType), runner: RunnerType,
|
|
92
|
+
});
|
|
93
|
+
const { form: _form, programIr: _programIr, argTypes: _argTypes, ...older } = mutation;
|
|
94
|
+
assert.throws(
|
|
95
|
+
() => decodeMutationObject(encodeBeast2For(PreDeltaMutationType)({ ...older, argTypes: mutation.argTypes })),
|
|
96
|
+
/^Error: the mutation object does not decode: the package was exported by an older e3 SDK — re-export it with the current one \(/,
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('refuses a mutation whose form is a String, as an older SDK wrote it, whatever the string', () => {
|
|
101
|
+
// A String form decoded as any string, and a form e3 does not know ran as
|
|
102
|
+
// whichever form its checks fell through to.
|
|
103
|
+
const StringFormMutationType = StructType({
|
|
104
|
+
bodyIr: StringType, argTypes: ArrayType(EastTypeType), runner: RunnerType, form: StringType, programIr: StringType,
|
|
105
|
+
});
|
|
106
|
+
for (const form of ['edit', 'bogus']) {
|
|
107
|
+
assert.throws(
|
|
108
|
+
() => decodeMutationObject(encodeBeast2For(StringFormMutationType)({ ...mutation, form })),
|
|
109
|
+
/^Error: the mutation object does not decode: the package was exported by an older e3 SDK — re-export it with the current one \(/,
|
|
110
|
+
form,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe('decodeMigrationObject', () => {
|
|
117
|
+
const RowV1Type = StructType({ name: StringType });
|
|
118
|
+
const RowV2Type = StructType({ name: StringType, shift: IntegerType });
|
|
119
|
+
const migration: MigrationObject = {
|
|
120
|
+
form: variant('rows', null),
|
|
121
|
+
from: toEastTypeValue(DictType(StringType, RowV1Type)),
|
|
122
|
+
to: toEastTypeValue(DictType(StringType, RowV2Type)),
|
|
123
|
+
bodyIr: 'a'.repeat(64),
|
|
124
|
+
programIr: 'b'.repeat(64),
|
|
125
|
+
runner: variant('east_node', { platforms: ['@elaraai/east-node-std'] }),
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
it('round-trips a current migration, its types among it', () => {
|
|
129
|
+
const decoded = decodeMigrationObject(encodeBeast2For(MigrationObjectType)(migration));
|
|
130
|
+
// A decoded type value carries no memoized identity, so only the East
|
|
131
|
+
// comparison says whether two of them are the same type.
|
|
132
|
+
assert.ok(isTypeValueEqual(decoded.from, migration.from));
|
|
133
|
+
assert.ok(isTypeValueEqual(decoded.to, migration.to));
|
|
134
|
+
assert.ok(!isTypeValueEqual(decoded.from, decoded.to));
|
|
135
|
+
const { from: _from, to: _to, ...rest } = decoded;
|
|
136
|
+
const { from: _f, to: _t, ...expected } = migration;
|
|
137
|
+
assert.deepEqual(rest, expected);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('refuses the bytes of another object, naming the re-export', () => {
|
|
141
|
+
const mutation: MutationObject = {
|
|
142
|
+
bodyIr: 'a'.repeat(64), argTypes: [], runner: migration.runner, form: variant('reduce', null), programIr: '',
|
|
143
|
+
};
|
|
144
|
+
assert.throws(
|
|
145
|
+
() => decodeMigrationObject(encodeBeast2For(MutationObjectType)(mutation)),
|
|
146
|
+
/^Error: the migration object does not decode: the package was exported by an older e3 SDK — re-export it with the current one \(/,
|
|
147
|
+
);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('refuses a migration whose form is a String, as an older SDK wrote it, whatever the string', () => {
|
|
151
|
+
// A deploy ran a String form it did not know as a split step.
|
|
152
|
+
const StringFormMigrationType = StructType({
|
|
153
|
+
form: StringType, from: EastTypeType, to: EastTypeType, bodyIr: StringType, programIr: StringType, runner: RunnerType,
|
|
154
|
+
});
|
|
155
|
+
for (const form of ['rows', 'bogus']) {
|
|
156
|
+
assert.throws(
|
|
157
|
+
() => decodeMigrationObject(encodeBeast2For(StringFormMigrationType)({ ...migration, form })),
|
|
158
|
+
/^Error: the migration object does not decode: the package was exported by an older e3 SDK — re-export it with the current one \(/,
|
|
159
|
+
form,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
describe('decodeRecordObject', () => {
|
|
166
|
+
const record: RecordObject = {
|
|
167
|
+
path: 'records/orders',
|
|
168
|
+
mutations: new Map([['place', 'a'.repeat(64)]]),
|
|
169
|
+
indexes: new Map([['by_status', 'b'.repeat(64)]]),
|
|
170
|
+
migrations: [{ name: 'add_shift', migration: 'c'.repeat(64) }, { name: 'widen_row', migration: 'd'.repeat(64) }],
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/** A decoded record object with its dicts as entry lists: a decoded Dict is
|
|
174
|
+
* a SortedMap, which is the same mapping and not the same container. */
|
|
175
|
+
const read = (bytes: Uint8Array) => {
|
|
176
|
+
const decoded = decodeRecordObject(bytes);
|
|
177
|
+
return { path: decoded.path, mutations: [...decoded.mutations], indexes: [...decoded.indexes], migrations: decoded.migrations };
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
it('round-trips a current record object, its migrations in their order', () => {
|
|
181
|
+
assert.deepEqual(read(encodeBeast2For(RecordObjectType)(record)),
|
|
182
|
+
{ path: record.path, mutations: [...record.mutations], indexes: [...record.indexes], migrations: record.migrations });
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('refuses a record object an older SDK exported before migrations, naming the fix', () => {
|
|
186
|
+
const IndexesEraRecordType = StructType({
|
|
187
|
+
path: StringType, mutations: DictType(StringType, StringType), indexes: DictType(StringType, StringType),
|
|
188
|
+
});
|
|
189
|
+
const { migrations: _migrations, ...older } = record;
|
|
190
|
+
assert.throws(
|
|
191
|
+
() => decodeRecordObject(encodeBeast2For(IndexesEraRecordType)(older)),
|
|
192
|
+
/^Error: the record object does not decode: the package was exported by an older e3 SDK — re-export it with the current one \(/,
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
});
|