@elaraai/e3-cloud-files 1.0.93 → 1.0.97
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 +129 -23
- package/dist/src/authoring.d.ts +61 -24
- package/dist/src/authoring.d.ts.map +1 -1
- package/dist/src/authoring.js +92 -48
- package/dist/src/authoring.js.map +1 -1
- package/dist/src/check/index.d.ts +3 -4
- package/dist/src/check/index.d.ts.map +1 -1
- package/dist/src/check/index.js +3 -4
- package/dist/src/check/index.js.map +1 -1
- package/dist/src/check.d.ts +44 -9
- package/dist/src/check.d.ts.map +1 -1
- package/dist/src/check.js +130 -53
- package/dist/src/check.js.map +1 -1
- package/dist/src/compat.d.ts +384 -0
- package/dist/src/compat.d.ts.map +1 -0
- package/dist/src/compat.js +258 -0
- package/dist/src/compat.js.map +1 -0
- package/dist/src/index.d.ts +12 -9
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +12 -9
- package/dist/src/index.js.map +1 -1
- package/dist/src/s3/index.d.ts.map +1 -1
- package/dist/src/s3/index.js +34 -12
- package/dist/src/s3/index.js.map +1 -1
- package/dist/src/storage/index.d.ts +72 -3
- package/dist/src/storage/index.d.ts.map +1 -1
- package/dist/src/storage/index.js +258 -13
- package/dist/src/storage/index.js.map +1 -1
- package/dist/src/sync/engine.d.ts +282 -49
- package/dist/src/sync/engine.d.ts.map +1 -1
- package/dist/src/sync/engine.js +599 -110
- package/dist/src/sync/engine.js.map +1 -1
- package/dist/src/sync/index.d.ts +3 -2
- package/dist/src/sync/index.d.ts.map +1 -1
- package/dist/src/sync/index.js +3 -2
- package/dist/src/sync/index.js.map +1 -1
- package/dist/src/sync/memory.d.ts +67 -12
- package/dist/src/sync/memory.d.ts.map +1 -1
- package/dist/src/sync/memory.js +248 -18
- package/dist/src/sync/memory.js.map +1 -1
- package/dist/src/sync/push.d.ts +56 -0
- package/dist/src/sync/push.d.ts.map +1 -0
- package/dist/src/sync/push.js +205 -0
- package/dist/src/sync/push.js.map +1 -0
- package/dist/test/typed.examples.d.ts +49 -0
- package/dist/test/typed.examples.d.ts.map +1 -0
- package/dist/test/typed.examples.js +58 -0
- package/dist/test/typed.examples.js.map +1 -0
- package/package.json +10 -6
package/dist/src/index.d.ts
CHANGED
|
@@ -4,24 +4,27 @@
|
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
6
|
* `@elaraai/e3-cloud-files`: a solution's inputs that come from files in a
|
|
7
|
-
* bucket.
|
|
7
|
+
* bucket, and its records seeded or fed from them.
|
|
8
8
|
*
|
|
9
9
|
* - `Files.source(name)`: a source, which each workspace binds to a bucket
|
|
10
10
|
* and a prefix; its `file`, `latest`, `folder` and `listing` declare the
|
|
11
|
-
* inputs its files fill, each an ordinary `e3.input
|
|
11
|
+
* inputs its files fill, each an ordinary `e3.input`, a `file` or `latest`
|
|
12
|
+
* typed when it names a `type`; its `mutation` declares a record's
|
|
13
|
+
* mutation fed by its files.
|
|
12
14
|
* - `Files.manifest(sources)`: the declarations `e3-cloud files deploy`
|
|
13
15
|
* deploys beside the package.
|
|
14
|
-
* - `checkFiles`, `checkBindings`, `
|
|
15
|
-
* and a bind run.
|
|
16
|
+
* - `checkFiles`, `checkBindings`, `readPackageSurface`: the checks a deploy
|
|
17
|
+
* and a bind run, against the package's inputs, records and mutations.
|
|
16
18
|
*
|
|
17
|
-
* A file arrives as it is, a `Blob`; the solution decodes it in
|
|
18
|
-
*
|
|
19
|
+
* A file with no type arrives as it is, a `Blob`; the solution decodes it in
|
|
20
|
+
* East. A typed file arrives as the beast2 value it holds. The sync is
|
|
21
|
+
* `@elaraai/e3-cloud-files/sync`; the East types are
|
|
19
22
|
* `@elaraai/e3-cloud-types`'s `Files*`.
|
|
20
23
|
*
|
|
21
24
|
* @packageDocumentation
|
|
22
25
|
*/
|
|
23
|
-
export { Files, FilesSource, FilesDeclarationError, type FilesInput, type FilesSelection } from './authoring.js';
|
|
24
|
-
export { checkFiles, checkBindings, readPackageInputs, shapeType, type PackageInput, type ObjectReader, type BindingPolicy, } from './check.js';
|
|
26
|
+
export { Files, FilesSource, FilesDeclarationError, type FilesInput, type FilesMutation, type FilesSelection, type FilesKeySpec, type FilesTypedKeySpec, type FilesTypedSelection, } from './authoring.js';
|
|
27
|
+
export { checkFiles, checkBindings, readPackageInputs, readPackageSurface, shapeType, type PackageInput, type PackageRecord, type PackageMutation, type PackageSurface, type ObjectReader, type BindingPolicy, } from './check.js';
|
|
25
28
|
export { globMatcher, parseBucketUrl, keyProblem, prefixProblem, includeProblem, type BucketLocation } from './paths.js';
|
|
26
|
-
export { FilesManifestType, FilesMappingType, FilesShapeType, FilesBindingType, FilesSyncReportType, FilesMappingReportType, FilesStatusType, FilesProblemType, FilesListingRowType, FilesFileType, FilesFolderType, FilesListingType, FILES_FILE_MAX_BYTES, FILES_FOLDER_ENTRY_MAX_BYTES, filesProblemText, type FilesManifest, type FilesMapping, type FilesShape, type FilesBinding, type FilesSyncReport, type FilesMappingReport, type FilesStatus, type FilesProblem, type FilesListingRow, } from '@elaraai/e3-cloud-types';
|
|
29
|
+
export { FilesManifestType, FilesMappingType, FilesShapeType, FilesTargetType, FilesBindingType, FilesSyncReportType, FilesMappingReportType, FilesRefusalReasonType, FilesStatusType, FilesProblemType, FilesListingRowType, FilesFileType, FilesFolderType, FilesListingType, FILES_FILE_MAX_BYTES, FILES_FOLDER_ENTRY_MAX_BYTES, FILES_TYPED_VALUE_MAX_BYTES, FILES_SEGMENT_MAX_BYTES, filesProblemText, filesRefusalText, filesTargetText, parseFilesTarget, sameFilesTarget, type FilesManifest, type FilesMapping, type FilesShape, type FilesTarget, type FilesBinding, type FilesSyncReport, type FilesMappingReport, type FilesRefusalReason, type FilesStatus, type FilesProblem, type FilesListingRow, } from '@elaraai/e3-cloud-types';
|
|
27
30
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/src/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EACL,KAAK,EAAE,WAAW,EAAE,qBAAqB,EACzC,KAAK,UAAU,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,KAAK,YAAY,EAAE,KAAK,iBAAiB,EAAE,KAAK,mBAAmB,GAC9H,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,SAAS,EACT,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,aAAa,GACnB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,aAAa,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AACzH,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,sBAAsB,EACtB,sBAAsB,EACtB,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,4BAA4B,EAC5B,2BAA2B,EAC3B,uBAAuB,EACvB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,eAAe,EACf,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,eAAe,GACrB,MAAM,yBAAyB,CAAC"}
|
package/dist/src/index.js
CHANGED
|
@@ -4,24 +4,27 @@
|
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
6
|
* `@elaraai/e3-cloud-files`: a solution's inputs that come from files in a
|
|
7
|
-
* bucket.
|
|
7
|
+
* bucket, and its records seeded or fed from them.
|
|
8
8
|
*
|
|
9
9
|
* - `Files.source(name)`: a source, which each workspace binds to a bucket
|
|
10
10
|
* and a prefix; its `file`, `latest`, `folder` and `listing` declare the
|
|
11
|
-
* inputs its files fill, each an ordinary `e3.input
|
|
11
|
+
* inputs its files fill, each an ordinary `e3.input`, a `file` or `latest`
|
|
12
|
+
* typed when it names a `type`; its `mutation` declares a record's
|
|
13
|
+
* mutation fed by its files.
|
|
12
14
|
* - `Files.manifest(sources)`: the declarations `e3-cloud files deploy`
|
|
13
15
|
* deploys beside the package.
|
|
14
|
-
* - `checkFiles`, `checkBindings`, `
|
|
15
|
-
* and a bind run.
|
|
16
|
+
* - `checkFiles`, `checkBindings`, `readPackageSurface`: the checks a deploy
|
|
17
|
+
* and a bind run, against the package's inputs, records and mutations.
|
|
16
18
|
*
|
|
17
|
-
* A file arrives as it is, a `Blob`; the solution decodes it in
|
|
18
|
-
*
|
|
19
|
+
* A file with no type arrives as it is, a `Blob`; the solution decodes it in
|
|
20
|
+
* East. A typed file arrives as the beast2 value it holds. The sync is
|
|
21
|
+
* `@elaraai/e3-cloud-files/sync`; the East types are
|
|
19
22
|
* `@elaraai/e3-cloud-types`'s `Files*`.
|
|
20
23
|
*
|
|
21
24
|
* @packageDocumentation
|
|
22
25
|
*/
|
|
23
|
-
export { Files, FilesSource, FilesDeclarationError } from './authoring.js';
|
|
24
|
-
export { checkFiles, checkBindings, readPackageInputs, shapeType, } from './check.js';
|
|
26
|
+
export { Files, FilesSource, FilesDeclarationError, } from './authoring.js';
|
|
27
|
+
export { checkFiles, checkBindings, readPackageInputs, readPackageSurface, shapeType, } from './check.js';
|
|
25
28
|
export { globMatcher, parseBucketUrl, keyProblem, prefixProblem, includeProblem } from './paths.js';
|
|
26
|
-
export { FilesManifestType, FilesMappingType, FilesShapeType, FilesBindingType, FilesSyncReportType, FilesMappingReportType, FilesStatusType, FilesProblemType, FilesListingRowType, FilesFileType, FilesFolderType, FilesListingType, FILES_FILE_MAX_BYTES, FILES_FOLDER_ENTRY_MAX_BYTES, filesProblemText, } from '@elaraai/e3-cloud-types';
|
|
29
|
+
export { FilesManifestType, FilesMappingType, FilesShapeType, FilesTargetType, FilesBindingType, FilesSyncReportType, FilesMappingReportType, FilesRefusalReasonType, FilesStatusType, FilesProblemType, FilesListingRowType, FilesFileType, FilesFolderType, FilesListingType, FILES_FILE_MAX_BYTES, FILES_FOLDER_ENTRY_MAX_BYTES, FILES_TYPED_VALUE_MAX_BYTES, FILES_SEGMENT_MAX_BYTES, filesProblemText, filesRefusalText, filesTargetText, parseFilesTarget, sameFilesTarget, } from '@elaraai/e3-cloud-types';
|
|
27
30
|
//# sourceMappingURL=index.js.map
|
package/dist/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EACL,KAAK,EAAE,WAAW,EAAE,qBAAqB,GAE1C,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,kBAAkB,EAClB,SAAS,GAOV,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,UAAU,EAAE,aAAa,EAAE,cAAc,EAAuB,MAAM,YAAY,CAAC;AACzH,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,sBAAsB,EACtB,sBAAsB,EACtB,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,4BAA4B,EAC5B,2BAA2B,EAC3B,uBAAuB,EACvB,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,eAAe,GAYhB,MAAM,yBAAyB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/s3/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAiF,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAClI,OAAO,EAA0C,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE7F,sFAAsF;AACtF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+BAA+B;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAgCD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,GAAG,WAAW,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/s3/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAiF,KAAK,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAClI,OAAO,EAA0C,KAAK,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAE7F,sFAAsF;AACtF,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,+BAA+B;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAgCD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,GAAG,WAAW,CAqFjF"}
|
package/dist/src/s3/index.js
CHANGED
|
@@ -63,6 +63,22 @@ function unreadable(err) {
|
|
|
63
63
|
*/
|
|
64
64
|
export function s3FilesReader(client, location) {
|
|
65
65
|
const absolute = (key) => `${location.prefix}${key}`;
|
|
66
|
+
/** A file, or a range of it, at the version its listing gave; a refusal of the file's own is unreadable. */
|
|
67
|
+
const get = async (file, range) => {
|
|
68
|
+
let object;
|
|
69
|
+
try {
|
|
70
|
+
object = await client.send(new GetObjectCommand({ Bucket: location.bucket, Key: absolute(file.key), IfMatch: `"${file.etag}"`, ...(range !== undefined && { Range: range }) }));
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
const reason = unreadable(err);
|
|
74
|
+
if (reason !== null)
|
|
75
|
+
throw new FilesUnreadableError(file.key, reason);
|
|
76
|
+
throw err;
|
|
77
|
+
}
|
|
78
|
+
if (object.Body === undefined)
|
|
79
|
+
throw new Error(`s3://${location.bucket}/${absolute(file.key)} answered no bytes`);
|
|
80
|
+
return object;
|
|
81
|
+
};
|
|
66
82
|
return {
|
|
67
83
|
async *list(prefix, options = {}) {
|
|
68
84
|
let token;
|
|
@@ -114,18 +130,7 @@ export function s3FilesReader(client, location) {
|
|
|
114
130
|
return { key, size: object.Size ?? 0, modified: object.LastModified ?? new Date(0), etag: bare(object.ETag) };
|
|
115
131
|
},
|
|
116
132
|
async read(file) {
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
object = await client.send(new GetObjectCommand({ Bucket: location.bucket, Key: absolute(file.key), IfMatch: `"${file.etag}"` }));
|
|
120
|
-
}
|
|
121
|
-
catch (err) {
|
|
122
|
-
const reason = unreadable(err);
|
|
123
|
-
if (reason !== null)
|
|
124
|
-
throw new FilesUnreadableError(file.key, reason);
|
|
125
|
-
throw err;
|
|
126
|
-
}
|
|
127
|
-
if (object.Body === undefined)
|
|
128
|
-
throw new Error(`s3://${location.bucket}/${absolute(file.key)} answered no bytes`);
|
|
133
|
+
const object = await get(file);
|
|
129
134
|
if (object.ContentLength !== undefined && object.ContentLength !== file.size) {
|
|
130
135
|
// Never buffered: its size was never checked
|
|
131
136
|
object.Body.destroy?.();
|
|
@@ -133,6 +138,23 @@ export function s3FilesReader(client, location) {
|
|
|
133
138
|
}
|
|
134
139
|
return object.Body.transformToByteArray();
|
|
135
140
|
},
|
|
141
|
+
async readRange(file, start, end) {
|
|
142
|
+
if (end <= start)
|
|
143
|
+
return new Uint8Array(0);
|
|
144
|
+
// A range's end is inclusive to the bucket
|
|
145
|
+
const object = await get(file, `bytes=${start}-${end - 1}`);
|
|
146
|
+
return object.Body.transformToByteArray();
|
|
147
|
+
},
|
|
148
|
+
async open(file, options = {}) {
|
|
149
|
+
const start = options.start ?? 0;
|
|
150
|
+
const object = await get(file, start > 0 ? `bytes=${start}-` : undefined);
|
|
151
|
+
if (object.ContentLength !== undefined && object.ContentLength !== file.size - start) {
|
|
152
|
+
object.Body.destroy?.();
|
|
153
|
+
throw new FilesUnreadableError(file.key, `changed since it was listed (${object.ContentLength + start} bytes, not the ${file.size} listed): the next sync takes it`);
|
|
154
|
+
}
|
|
155
|
+
// A bucket's body is a stream of chunks
|
|
156
|
+
return object.Body;
|
|
157
|
+
},
|
|
136
158
|
};
|
|
137
159
|
}
|
|
138
160
|
//# sourceMappingURL=index.js.map
|
package/dist/src/s3/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/s3/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,kBAAkB,EAAiB,MAAM,oBAAoB,CAAC;AAClI,OAAO,EAAE,oBAAoB,EAAsC,MAAM,mBAAmB,CAAC;AAS7F,yDAAyD;AACzD,SAAS,IAAI,CAAC,IAAwB;IACpC,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED,6DAA6D;AAC7D,SAAS,OAAO,CAAC,GAAY;IAC3B,OAAO,GAAG,YAAY,kBAAkB,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,UAAU,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW,IAAI,GAAG,CAAC,SAAS,CAAC,cAAc,KAAK,GAAG,CAAC,CAAC;AAC5I,CAAC;AAED,+DAA+D;AAC/D,SAAS,MAAM,CAAC,GAAY;IAC1B,OAAO,GAAG,YAAY,kBAAkB,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,cAAc,IAAI,GAAG,CAAC,SAAS,CAAC,cAAc,KAAK,GAAG,CAAC,CAAC;AACpH,CAAC;AAED;;;GAGG;AACH,SAAS,UAAU,CAAC,GAAY;IAC9B,IAAI,CAAC,CAAC,GAAG,YAAY,kBAAkB,CAAC;QAAE,OAAO,IAAI,CAAC;IACtD,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;QACjB,KAAK,oBAAoB,CAAC,CAAC,OAAO,qDAAqD,CAAC;QACxF,KAAK,oBAAoB,CAAC,CAAC,OAAO,uEAAuE,CAAC;QAC1G,KAAK,cAAc,CAAC,CAAC,OAAO,kFAAkF,CAAC;QAC/G,KAAK,WAAW,CAAC,CAAC,OAAO,6BAA6B,CAAC;QACvD,OAAO,CAAC,CAAC,OAAO,GAAG,CAAC,SAAS,CAAC,cAAc,KAAK,GAAG,CAAC,CAAC,CAAC,6BAA6B,CAAC,CAAC,CAAC,IAAI,CAAC;IAC9F,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa,CAAC,MAAgB,EAAE,QAAoB;IAClE,MAAM,QAAQ,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;IAC7D,OAAO;QACL,KAAK,CAAC,CAAC,IAAI,CAAC,MAAM,EAAE,OAAO,GAAG,EAAE;YAC9B,IAAI,KAAyB,CAAC;YAC9B,GAAG,CAAC;gBACF,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,oBAAoB,CAAC;oBACtD,MAAM,EAAE,QAAQ,CAAC,MAAM;oBACvB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC;oBACxB,iBAAiB,EAAE,KAAK;oBACxB,4EAA4E;oBAC5E,GAAG,CAAC,OAAO,CAAC,aAAa,KAAK,IAAI,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;iBAC1D,CAAC,CAAC,CAAC;gBACJ,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,QAAQ,IAAI,EAAE,EAAE,CAAC;oBACzC,IAAI,MAAM,CAAC,GAAG,KAAK,SAAS;wBAAE,SAAS;oBACvC,MAAM;wBACJ,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC;wBAC7C,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC;wBACtB,QAAQ,EAAE,MAAM,CAAC,YAAY,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC;wBAC5C,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;qBACxB,CAAC;gBACJ,CAAC;gBACD,KAAK,GAAG,IAAI,CAAC,WAAW,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC,CAAC,SAAS,CAAC;YAC7E,CAAC,QAAQ,KAAK,KAAK,SAAS,EAAE;QAChC,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,GAAG;YACZ,qEAAqE;YACrE,kDAAkD;YAClD,IAAI,IAAI,CAAC;YACT,IAAI,CAAC;gBACH,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,oBAAoB,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YACrH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;oBAAE,MAAM,GAAG,CAAC;gBAC5B,mFAAmF;gBACnF,IAAI,CAAC;oBACH,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,IAAI,iBAAiB,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;oBACvG,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,aAAa,IAAI,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,YAAY,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACnH,CAAC;gBAAC,OAAO,OAAO,EAAE,CAAC;oBACjB,IAAI,OAAO,CAAC,OAAO,CAAC;wBAAE,OAAO,IAAI,CAAC;oBAClC,MAAM,OAAO,CAAC;gBAChB,CAAC;YACH,CAAC;YACD,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC;YAClC,IAAI,MAAM,EAAE,GAAG,KAAK,QAAQ,CAAC,GAAG,CAAC;gBAAE,OAAO,IAAI,CAAC;YAC/C,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,YAAY,IAAI,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QAChH,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,IAAI;YACb,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/s3/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,15 +2,84 @@
|
|
|
2
2
|
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
3
|
* Licensed under BSL 1.1. See LICENSE for details.
|
|
4
4
|
*/
|
|
5
|
-
import { type StorageBackend } from '@elaraai/e3-core';
|
|
6
|
-
import type
|
|
5
|
+
import { type StorageBackend, type TaskRunner } from '@elaraai/e3-core';
|
|
6
|
+
import { type FilesRefusalReason } from '@elaraai/e3-cloud-types';
|
|
7
|
+
import { type FilesWriter } from '../sync/engine.js';
|
|
8
|
+
/** What the writer is given beside the store. */
|
|
9
|
+
export interface StorageWriterOptions {
|
|
10
|
+
/** The runner that takes a typed collection in by pieces, and runs a fed mutation; without one, a typed file that is a collection the store does not know, or a feed, is refused */
|
|
11
|
+
readonly runner?: TaskRunner;
|
|
12
|
+
/** Where a typed file's bytes are streamed to before e3 takes them in: a directory of the caller's (default the system's temporary directory) */
|
|
13
|
+
readonly scratchDir?: string;
|
|
14
|
+
/** Who a feed's commit records as its actor (default `files`) */
|
|
15
|
+
readonly actor?: string;
|
|
16
|
+
/** How long a fed mutation may run, in ms (default e3's) */
|
|
17
|
+
readonly feedBudgetMs?: number;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A refusal e3's door answered, as the sync reports it; null for an error the
|
|
21
|
+
* door did not refuse.
|
|
22
|
+
*
|
|
23
|
+
* @param err - What the door threw
|
|
24
|
+
* @param type - The mapping's type
|
|
25
|
+
* @returns The refusal, or null
|
|
26
|
+
*/
|
|
27
|
+
export declare function doorRefusal(err: unknown): FilesRefusalReason | null;
|
|
7
28
|
/**
|
|
8
29
|
* The sync's writer over an e3 store, for one workspace.
|
|
9
30
|
*
|
|
10
31
|
* @param storage - The store: a local repository's (`LocalStorage`) or the platform's
|
|
11
32
|
* @param repo - The repository: a local repository's path, or the platform's repository name
|
|
12
33
|
* @param ws - The workspace
|
|
34
|
+
* @param options - The runner, the scratch directory and the actor
|
|
35
|
+
* @returns The writer
|
|
36
|
+
*/
|
|
37
|
+
export declare function storageWriter(storage: StorageBackend, repo: string, ws: string, options?: StorageWriterOptions): FilesWriter;
|
|
38
|
+
/**
|
|
39
|
+
* Stages a typed file's bytes in the object store as the object their
|
|
40
|
+
* SHA-256 names, hashing them as they stream: the platform's typed intake,
|
|
41
|
+
* never on a disk. A stage the signal stops keeps nothing: the next round
|
|
42
|
+
* starts the file again.
|
|
43
|
+
*/
|
|
44
|
+
export interface FilesStaging {
|
|
45
|
+
/**
|
|
46
|
+
* @param repo - The repository, as the storage names it
|
|
47
|
+
* @param open - The file's bytes, from a byte on: opened at 0, or where a stage left off
|
|
48
|
+
* @param size - The file's size, as its listing gave it
|
|
49
|
+
* @param options - The round's signal, and where an earlier stage of the same file stood (its `progress`)
|
|
50
|
+
* @returns The bytes' SHA-256, the object placed; or `aborted` when the
|
|
51
|
+
* signal stopped it, with where it stood for the next round to resume
|
|
52
|
+
* (null when nothing was kept)
|
|
53
|
+
*/
|
|
54
|
+
stage(repo: string, open: (start: number) => Promise<AsyncIterable<Uint8Array>>, size: number, options: {
|
|
55
|
+
readonly signal?: AbortSignal;
|
|
56
|
+
readonly resume?: string;
|
|
57
|
+
}): Promise<{
|
|
58
|
+
sha256: string;
|
|
59
|
+
} | {
|
|
60
|
+
aborted: true;
|
|
61
|
+
progress: string | null;
|
|
62
|
+
}>;
|
|
63
|
+
}
|
|
64
|
+
/** What the cloud's writer is given beside the store. */
|
|
65
|
+
export interface CloudWriterOptions extends Omit<StorageWriterOptions, 'scratchDir'> {
|
|
66
|
+
/** The round's runner: takes a typed collection in by pieces, runs a fed mutation */
|
|
67
|
+
readonly runner: TaskRunner;
|
|
68
|
+
/** Where a typed file's bytes are staged: the platform's object store */
|
|
69
|
+
readonly staging: FilesStaging;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The sync's writer for a deployed workspace: the store's writer, with a
|
|
73
|
+
* typed file taken in through the platform's staging and e3's door for a
|
|
74
|
+
* delivery the store holds (`datasetAdoptObject`), so no byte of it passes
|
|
75
|
+
* through a disk (D7), and an intake the round's deadline stops resumes in
|
|
76
|
+
* the next round with the pieces it finished remembered.
|
|
77
|
+
*
|
|
78
|
+
* @param storage - The platform's storage
|
|
79
|
+
* @param repo - The repository, as the storage names it
|
|
80
|
+
* @param ws - The workspace
|
|
81
|
+
* @param options - The runner, the staging, the actor
|
|
13
82
|
* @returns The writer
|
|
14
83
|
*/
|
|
15
|
-
export declare function
|
|
84
|
+
export declare function cloudWriter(storage: StorageBackend, repo: string, ws: string, options: CloudWriterOptions): FilesWriter;
|
|
16
85
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/storage/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/storage/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AA+CH,OAAO,EAcL,KAAK,cAAc,EACnB,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAA0E,KAAK,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAC1I,OAAO,EAEkE,KAAK,WAAW,EAExF,MAAM,mBAAmB,CAAC;AAwD3B,iDAAiD;AACjD,MAAM,WAAW,oBAAoB;IACnC,oLAAoL;IACpL,QAAQ,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC;IAC7B,iJAAiJ;IACjJ,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,iEAAiE;IACjE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,4DAA4D;IAC5D,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,kBAAkB,GAAG,IAAI,CASnE;AA2BD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,GAAE,oBAAyB,GAAG,WAAW,CA2GhI;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;;;;;OAQG;IACH,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;CAChP;AAED,yDAAyD;AACzD,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,oBAAoB,EAAE,YAAY,CAAC;IAClF,qFAAqF;IACrF,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC;CAChC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAqDvH"}
|
|
@@ -4,30 +4,61 @@
|
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
6
|
* `@elaraai/e3-cloud-files/storage`: the sync's writer over an e3 store, the
|
|
7
|
-
* one a local repository and
|
|
7
|
+
* one a local repository writes through, and the base of a deployed
|
|
8
|
+
* workspace's.
|
|
8
9
|
*
|
|
9
|
-
* A `file`, `latest` or `listing` input is written as any input
|
|
10
|
-
* (`workspaceSetDataset`). A `folder` is updated a piece at a time: e3
|
|
11
|
-
* a collection as pieces, each holding the entries between two keys,
|
|
12
|
-
* the pieces holding a changed or removed file are read and cut
|
|
13
|
-
* every other piece is carried over as it is stored
|
|
14
|
-
* read. However large the folder, a sync holds
|
|
15
|
-
* files it read. The new folder is then set as
|
|
16
|
-
* for a value the store holds
|
|
17
|
-
* holds the dataset's write lock
|
|
10
|
+
* A `file`, `latest` or `listing` input with no type is written as any input
|
|
11
|
+
* is (`workspaceSetDataset`). A `folder` is updated a piece at a time: e3
|
|
12
|
+
* stores a collection as pieces, each holding the entries between two keys,
|
|
13
|
+
* so only the pieces holding a changed or removed file are read and cut
|
|
14
|
+
* again, and every other piece is carried over as it is stored
|
|
15
|
+
* (`storeCollection`), never read. However large the folder, a sync holds
|
|
16
|
+
* the pieces it changes and the files it read. The new folder is then set as
|
|
17
|
+
* the input's value by e3's door for a value the store holds
|
|
18
|
+
* (`datasetSetStored`), which checks its type, holds the dataset's write lock
|
|
19
|
+
* and versions it as any input write.
|
|
20
|
+
*
|
|
21
|
+
* A typed file is taken in through e3's door for a file (`datasetAdoptFile`):
|
|
22
|
+
* its bytes are streamed to a file of the repository's own scratch, hashed as
|
|
23
|
+
* they go, and the door takes the file in as the value it holds, a collection
|
|
24
|
+
* by intake units on the runner given, remembered by the bytes' hash so the
|
|
25
|
+
* same bytes taken again cost their hash and nothing else. (A local
|
|
26
|
+
* repository may use its disk; the platform's writer stages in its bucket
|
|
27
|
+
* instead, and never on a disk.) A fed mutation runs through e3's own
|
|
28
|
+
* `recordMutate`, the file's hash its idempotency key, so a file applied
|
|
29
|
+
* once is never applied twice.
|
|
30
|
+
*
|
|
31
|
+
* {@link cloudWriter} is the platform's: the same, but a typed file staged
|
|
32
|
+
* in the object store through {@link FilesStaging} (never a disk) and taken
|
|
33
|
+
* in through e3's door for a delivery the store holds, resumable across the
|
|
34
|
+
* platform's rounds.
|
|
18
35
|
*
|
|
19
36
|
* Needs `@elaraai/e3-core`, which a solution's own code never imports: the
|
|
20
37
|
* CLI and the platform do.
|
|
21
38
|
*
|
|
22
39
|
* @packageDocumentation
|
|
23
40
|
*/
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
41
|
+
import { createHash } from 'node:crypto';
|
|
42
|
+
import { createWriteStream } from 'node:fs';
|
|
43
|
+
import { mkdir, rm } from 'node:fs/promises';
|
|
44
|
+
import { tmpdir } from 'node:os';
|
|
45
|
+
import { join } from 'node:path';
|
|
46
|
+
import { Readable } from 'node:stream';
|
|
47
|
+
import { pipeline } from 'node:stream/promises';
|
|
48
|
+
import { StringType, compareFor, decodeBeast2For, none, some, variant } from '@elaraai/east';
|
|
49
|
+
import { DatasetSegments, DatasetTypeMismatchError, DeliveryRefusedError, ObjectNotFoundError, datasetAdoptFile, datasetAdoptObject, datasetSetStored, recordHistory, recordMutate, storeCollection, workspaceGetDatasetHash, workspaceSetDataset, } from '@elaraai/e3-core';
|
|
26
50
|
import { FilesFileType, FilesFolderType, FilesListingType } from '@elaraai/e3-cloud-types';
|
|
51
|
+
import { FilesUnreadableError, } from '../sync/engine.js';
|
|
27
52
|
/** An input's path: `.inputs.<input>`. */
|
|
28
53
|
function inputPath(input) {
|
|
29
54
|
return [variant('field', 'inputs'), variant('field', input)];
|
|
30
55
|
}
|
|
56
|
+
/** A record's path: `.records.<record>`. */
|
|
57
|
+
function recordPath(record) {
|
|
58
|
+
return [variant('field', 'records'), variant('field', record)];
|
|
59
|
+
}
|
|
60
|
+
/** The most commits a seed walks back for a record's last seed. */
|
|
61
|
+
const SEED_WALK_LIMIT = 100;
|
|
31
62
|
const compareKeys = compareFor(StringType);
|
|
32
63
|
const decodeFolder = decodeBeast2For(FilesFolderType);
|
|
33
64
|
/** Entries in a Dict's order. */
|
|
@@ -73,15 +104,61 @@ async function folderSources(storage, repo, hash, changes) {
|
|
|
73
104
|
sources.push({ stored: pieces.hash, from: next, to: pieces.counts.length });
|
|
74
105
|
return sources;
|
|
75
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* A refusal e3's door answered, as the sync reports it; null for an error the
|
|
109
|
+
* door did not refuse.
|
|
110
|
+
*
|
|
111
|
+
* @param err - What the door threw
|
|
112
|
+
* @param type - The mapping's type
|
|
113
|
+
* @returns The refusal, or null
|
|
114
|
+
*/
|
|
115
|
+
export function doorRefusal(err) {
|
|
116
|
+
if (err instanceof DatasetTypeMismatchError)
|
|
117
|
+
return variant('type_mismatch', { expected: err.mismatch.declared, actual: err.mismatch.given });
|
|
118
|
+
if (err instanceof DeliveryRefusedError) {
|
|
119
|
+
// east's intake: "segment N of the delivery, at offset X, holds B bytes, more than the M a segment is read in — write it again …"
|
|
120
|
+
const segment = /holds ([0-9]+) bytes, more than the ([0-9]+) a segment/i.exec(err.refusal);
|
|
121
|
+
if (segment !== null)
|
|
122
|
+
return variant('segment_too_large', { bytes: BigInt(Number(segment[1])), limit: BigInt(Number(segment[2])) });
|
|
123
|
+
return variant('other', `the runner refused it: ${err.refusal}`);
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Streams a file's bytes to a scratch file, hashing them as they go.
|
|
129
|
+
*
|
|
130
|
+
* @returns The scratch file's path and the bytes' SHA-256
|
|
131
|
+
*/
|
|
132
|
+
async function spool(source, file, dir, signal) {
|
|
133
|
+
await mkdir(dir, { recursive: true });
|
|
134
|
+
const path = join(dir, `files-${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}.beast2`);
|
|
135
|
+
const hash = createHash('sha256');
|
|
136
|
+
const stream = Readable.from(await source.open(file));
|
|
137
|
+
const hashing = async function* (chunks) {
|
|
138
|
+
for await (const chunk of chunks) {
|
|
139
|
+
hash.update(chunk);
|
|
140
|
+
yield chunk;
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
try {
|
|
144
|
+
await pipeline(stream, hashing, createWriteStream(path), signal === undefined ? {} : { signal });
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
await rm(path, { force: true });
|
|
148
|
+
throw err;
|
|
149
|
+
}
|
|
150
|
+
return { path, sha256: hash.digest('hex') };
|
|
151
|
+
}
|
|
76
152
|
/**
|
|
77
153
|
* The sync's writer over an e3 store, for one workspace.
|
|
78
154
|
*
|
|
79
155
|
* @param storage - The store: a local repository's (`LocalStorage`) or the platform's
|
|
80
156
|
* @param repo - The repository: a local repository's path, or the platform's repository name
|
|
81
157
|
* @param ws - The workspace
|
|
158
|
+
* @param options - The runner, the scratch directory and the actor
|
|
82
159
|
* @returns The writer
|
|
83
160
|
*/
|
|
84
|
-
export function storageWriter(storage, repo, ws) {
|
|
161
|
+
export function storageWriter(storage, repo, ws, options = {}) {
|
|
85
162
|
const current = async (input) => (await workspaceGetDatasetHash(storage, repo, ws, inputPath(input))).hash;
|
|
86
163
|
const written = async (input) => {
|
|
87
164
|
const hash = await current(input);
|
|
@@ -89,6 +166,7 @@ export function storageWriter(storage, repo, ws) {
|
|
|
89
166
|
throw new Error(`input ${input} is unassigned after it was written`);
|
|
90
167
|
return hash;
|
|
91
168
|
};
|
|
169
|
+
const scratch = options.scratchDir ?? tmpdir();
|
|
92
170
|
return {
|
|
93
171
|
current,
|
|
94
172
|
async setFile(input, bytes) {
|
|
@@ -108,6 +186,173 @@ export function storageWriter(storage, repo, ws) {
|
|
|
108
186
|
// sets it as the input's value, checked and versioned, reading nothing
|
|
109
187
|
return (await datasetSetStored(storage, repo, ws, inputPath(input), stored)).hash;
|
|
110
188
|
},
|
|
189
|
+
async takeTyped(input, file, type, source, intake) {
|
|
190
|
+
let spooled;
|
|
191
|
+
try {
|
|
192
|
+
spooled = await spool(source, file, scratch, intake.signal);
|
|
193
|
+
}
|
|
194
|
+
catch (err) {
|
|
195
|
+
if (err instanceof FilesUnreadableError)
|
|
196
|
+
return { status: 'refused', reason: variant('unreadable', err.reason) };
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const adopted = await datasetAdoptFile(storage, repo, ws, inputPath(input), spooled.path, {
|
|
201
|
+
expectHash: spooled.sha256,
|
|
202
|
+
...(options.runner !== undefined && { runner: options.runner }),
|
|
203
|
+
...(intake.signal !== undefined && { signal: intake.signal }),
|
|
204
|
+
});
|
|
205
|
+
return {
|
|
206
|
+
status: 'taken',
|
|
207
|
+
hash: adopted.hash,
|
|
208
|
+
sha256: spooled.sha256,
|
|
209
|
+
taken: { key: file.key, type, rows: adopted.rows === null ? none : some(BigInt(adopted.rows)), how: variant(adopted.segments === null ? 'carried' : 'split', null) },
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
catch (err) {
|
|
213
|
+
const reason = doorRefusal(err);
|
|
214
|
+
if (reason !== null)
|
|
215
|
+
return { status: 'refused', reason };
|
|
216
|
+
throw err;
|
|
217
|
+
}
|
|
218
|
+
finally {
|
|
219
|
+
await rm(spooled.path, { force: true });
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
async recordState(record) {
|
|
223
|
+
const { hash } = await workspaceGetDatasetHash(storage, repo, ws, recordPath(record));
|
|
224
|
+
if (hash === null)
|
|
225
|
+
return { pending: true };
|
|
226
|
+
// The head, and the commits since the last seed, walked back a bounded way
|
|
227
|
+
const history = await recordHistory(storage, repo, ws, record, { limit: SEED_WALK_LIMIT });
|
|
228
|
+
const head = history[0];
|
|
229
|
+
if (head === undefined)
|
|
230
|
+
return { pending: true };
|
|
231
|
+
const seed = history.findIndex((entry) => entry.commit.mutation === '$init' || entry.commit.mutation === '$seed');
|
|
232
|
+
return { pending: false, head: head.hash, sinceSeed: seed < 0 ? null : seed };
|
|
233
|
+
},
|
|
234
|
+
async feed(record, mutation, file, type, source, feed) {
|
|
235
|
+
if (options.runner === undefined)
|
|
236
|
+
throw new Error('a fed mutation needs a runner');
|
|
237
|
+
// A record with no state yet: nothing is read for it
|
|
238
|
+
const { hash } = await workspaceGetDatasetHash(storage, repo, ws, recordPath(record));
|
|
239
|
+
if (hash === null)
|
|
240
|
+
return { status: 'waiting', reason: 'the record has no state yet' };
|
|
241
|
+
let bytes;
|
|
242
|
+
try {
|
|
243
|
+
const chunks = [];
|
|
244
|
+
for await (const chunk of await source.open(file))
|
|
245
|
+
chunks.push(chunk);
|
|
246
|
+
bytes = Buffer.concat(chunks);
|
|
247
|
+
}
|
|
248
|
+
catch (err) {
|
|
249
|
+
if (err instanceof FilesUnreadableError)
|
|
250
|
+
return { status: 'refused', reason: variant('unreadable', err.reason), sha256: '' };
|
|
251
|
+
throw err;
|
|
252
|
+
}
|
|
253
|
+
const sha256 = createHash('sha256').update(bytes).digest('hex');
|
|
254
|
+
// Bytes applied before, under any name, are applied no more
|
|
255
|
+
if (feed.applied?.has(sha256) === true)
|
|
256
|
+
return { status: 'applied', sha256 };
|
|
257
|
+
const outcome = await recordMutate(storage, options.runner, repo, ws, record, mutation, [bytes], {
|
|
258
|
+
actor: options.actor ?? 'files',
|
|
259
|
+
idempotencyKey: sha256,
|
|
260
|
+
...(options.feedBudgetMs !== undefined && { budgetMs: options.feedBudgetMs }),
|
|
261
|
+
...(feed.signal !== undefined && { signal: feed.signal }),
|
|
262
|
+
});
|
|
263
|
+
void type;
|
|
264
|
+
switch (outcome.kind) {
|
|
265
|
+
case 'committed':
|
|
266
|
+
return { status: 'committed', commit: outcome.commitHash, sha256 };
|
|
267
|
+
case 'conflict':
|
|
268
|
+
// The record was busy: the next sync tries the file again
|
|
269
|
+
return { status: 'waiting', reason: `the record was busy (${outcome.detail ?? `the write lost the race ${outcome.attempts} times`}): tried again by the next sync` };
|
|
270
|
+
case 'invalid':
|
|
271
|
+
return { status: 'refused', reason: variant('mutation', { outcome: 'invalid', message: outcome.message }), sha256 };
|
|
272
|
+
case 'failed':
|
|
273
|
+
// Cut short by the round's end: not the file's fault
|
|
274
|
+
if (feed.signal?.aborted === true)
|
|
275
|
+
return { status: 'waiting', reason: 'the round ended before the mutation finished: tried again by the next sync' };
|
|
276
|
+
return { status: 'refused', reason: variant('mutation', { outcome: 'failed', message: outcome.stderr.trim().split('\n').slice(-3).join(' ') }), sha256 };
|
|
277
|
+
case 'timed_out':
|
|
278
|
+
return { status: 'refused', reason: variant('mutation', { outcome: 'timed_out', message: `after ${outcome.ms} ms` }), sha256 };
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* The sync's writer for a deployed workspace: the store's writer, with a
|
|
285
|
+
* typed file taken in through the platform's staging and e3's door for a
|
|
286
|
+
* delivery the store holds (`datasetAdoptObject`), so no byte of it passes
|
|
287
|
+
* through a disk (D7), and an intake the round's deadline stops resumes in
|
|
288
|
+
* the next round with the pieces it finished remembered.
|
|
289
|
+
*
|
|
290
|
+
* @param storage - The platform's storage
|
|
291
|
+
* @param repo - The repository, as the storage names it
|
|
292
|
+
* @param ws - The workspace
|
|
293
|
+
* @param options - The runner, the staging, the actor
|
|
294
|
+
* @returns The writer
|
|
295
|
+
*/
|
|
296
|
+
export function cloudWriter(storage, repo, ws, options) {
|
|
297
|
+
const base = storageWriter(storage, repo, ws, { runner: options.runner, ...(options.actor !== undefined && { actor: options.actor }), ...(options.feedBudgetMs !== undefined && { feedBudgetMs: options.feedBudgetMs }) });
|
|
298
|
+
return {
|
|
299
|
+
...base,
|
|
300
|
+
async takeTyped(input, file, type, source, intake) {
|
|
301
|
+
// The engine resumes only the intake of this same file, by its key and ETag
|
|
302
|
+
const progress = (sha256, staging) => ({ key: file.key, etag: file.etag, sha256, staging });
|
|
303
|
+
let sha256 = intake.resume?.sha256 ?? null;
|
|
304
|
+
const stage = async (resume) => {
|
|
305
|
+
let staged;
|
|
306
|
+
try {
|
|
307
|
+
staged = await options.staging.stage(repo, (start) => source.open(file, { start }), file.size, {
|
|
308
|
+
...(intake.signal !== undefined && { signal: intake.signal }),
|
|
309
|
+
...(resume !== null && { resume }),
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
catch (err) {
|
|
313
|
+
if (err instanceof FilesUnreadableError)
|
|
314
|
+
return { status: 'refused', reason: variant('unreadable', err.reason) };
|
|
315
|
+
throw err;
|
|
316
|
+
}
|
|
317
|
+
if ('aborted' in staged)
|
|
318
|
+
return { status: 'yielded', taking: staged.progress === null ? null : progress(null, staged.progress) };
|
|
319
|
+
return staged.sha256;
|
|
320
|
+
};
|
|
321
|
+
if (sha256 === null) {
|
|
322
|
+
const staged = await stage(intake.resume?.staging ?? null);
|
|
323
|
+
if (typeof staged !== 'string')
|
|
324
|
+
return staged;
|
|
325
|
+
sha256 = staged;
|
|
326
|
+
}
|
|
327
|
+
for (;;) {
|
|
328
|
+
try {
|
|
329
|
+
const adopted = await datasetAdoptObject(storage, repo, ws, inputPath(input), sha256, options.runner, intake.signal === undefined ? {} : { signal: intake.signal });
|
|
330
|
+
return {
|
|
331
|
+
status: 'taken',
|
|
332
|
+
hash: adopted.hash,
|
|
333
|
+
sha256,
|
|
334
|
+
taken: { key: file.key, type, rows: adopted.rows === null ? none : some(BigInt(adopted.rows)), how: variant(adopted.segments === null ? 'carried' : 'split', null) },
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
catch (err) {
|
|
338
|
+
// Stopped at the round's deadline, the pieces it finished remembered: the next round resumes it
|
|
339
|
+
if (intake.signal?.aborted === true)
|
|
340
|
+
return { status: 'yielded', taking: progress(sha256, null) };
|
|
341
|
+
// The delivery a resumed intake names is gone (collected since): staged again
|
|
342
|
+
if (err instanceof ObjectNotFoundError && intake.resume !== undefined) {
|
|
343
|
+
const staged = await stage(null);
|
|
344
|
+
if (typeof staged !== 'string')
|
|
345
|
+
return staged;
|
|
346
|
+
sha256 = staged;
|
|
347
|
+
continue;
|
|
348
|
+
}
|
|
349
|
+
const reason = doorRefusal(err);
|
|
350
|
+
if (reason !== null)
|
|
351
|
+
return { status: 'refused', reason };
|
|
352
|
+
throw err;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
},
|
|
111
356
|
};
|
|
112
357
|
}
|
|
113
358
|
//# sourceMappingURL=index.js.map
|