@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.
Files changed (49) hide show
  1. package/README.md +129 -23
  2. package/dist/src/authoring.d.ts +61 -24
  3. package/dist/src/authoring.d.ts.map +1 -1
  4. package/dist/src/authoring.js +92 -48
  5. package/dist/src/authoring.js.map +1 -1
  6. package/dist/src/check/index.d.ts +3 -4
  7. package/dist/src/check/index.d.ts.map +1 -1
  8. package/dist/src/check/index.js +3 -4
  9. package/dist/src/check/index.js.map +1 -1
  10. package/dist/src/check.d.ts +44 -9
  11. package/dist/src/check.d.ts.map +1 -1
  12. package/dist/src/check.js +130 -53
  13. package/dist/src/check.js.map +1 -1
  14. package/dist/src/compat.d.ts +384 -0
  15. package/dist/src/compat.d.ts.map +1 -0
  16. package/dist/src/compat.js +258 -0
  17. package/dist/src/compat.js.map +1 -0
  18. package/dist/src/index.d.ts +12 -9
  19. package/dist/src/index.d.ts.map +1 -1
  20. package/dist/src/index.js +12 -9
  21. package/dist/src/index.js.map +1 -1
  22. package/dist/src/s3/index.d.ts.map +1 -1
  23. package/dist/src/s3/index.js +34 -12
  24. package/dist/src/s3/index.js.map +1 -1
  25. package/dist/src/storage/index.d.ts +72 -3
  26. package/dist/src/storage/index.d.ts.map +1 -1
  27. package/dist/src/storage/index.js +258 -13
  28. package/dist/src/storage/index.js.map +1 -1
  29. package/dist/src/sync/engine.d.ts +282 -49
  30. package/dist/src/sync/engine.d.ts.map +1 -1
  31. package/dist/src/sync/engine.js +599 -110
  32. package/dist/src/sync/engine.js.map +1 -1
  33. package/dist/src/sync/index.d.ts +3 -2
  34. package/dist/src/sync/index.d.ts.map +1 -1
  35. package/dist/src/sync/index.js +3 -2
  36. package/dist/src/sync/index.js.map +1 -1
  37. package/dist/src/sync/memory.d.ts +67 -12
  38. package/dist/src/sync/memory.d.ts.map +1 -1
  39. package/dist/src/sync/memory.js +248 -18
  40. package/dist/src/sync/memory.js.map +1 -1
  41. package/dist/src/sync/push.d.ts +56 -0
  42. package/dist/src/sync/push.d.ts.map +1 -0
  43. package/dist/src/sync/push.js +205 -0
  44. package/dist/src/sync/push.js.map +1 -0
  45. package/dist/test/typed.examples.d.ts +49 -0
  46. package/dist/test/typed.examples.d.ts.map +1 -0
  47. package/dist/test/typed.examples.js +58 -0
  48. package/dist/test/typed.examples.js.map +1 -0
  49. package/package.json +10 -6
@@ -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`, `readPackageInputs`: the checks a deploy
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 East. The
18
- * sync is `@elaraai/e3-cloud-files/sync`; the East types are
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAE,KAAK,UAAU,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACjH,OAAO,EACL,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,SAAS,EACT,KAAK,YAAY,EACjB,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,gBAAgB,EAChB,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,4BAA4B,EAC5B,gBAAgB,EAChB,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,eAAe,GACrB,MAAM,yBAAyB,CAAC"}
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`, `readPackageInputs`: the checks a deploy
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 East. The
18
- * sync is `@elaraai/e3-cloud-files/sync`; the East types are
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,qBAAqB,EAAwC,MAAM,gBAAgB,CAAC;AACjH,OAAO,EACL,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,SAAS,GAIV,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,gBAAgB,EAChB,mBAAmB,EACnB,sBAAsB,EACtB,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,oBAAoB,EACpB,4BAA4B,EAC5B,gBAAgB,GAUjB,MAAM,yBAAyB,CAAC"}
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,CAgEjF"}
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"}
@@ -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
- let object;
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/s3/index.ts"],"names":[],"mappings":"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"}
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 { FilesWriter } from '../sync/engine.js';
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 storageWriter(storage: StorageBackend, repo: string, ws: string): FilesWriter;
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;AAuBH,OAAO,EAOL,KAAK,cAAc,EACpB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,KAAK,EAAE,WAAW,EAAiB,MAAM,mBAAmB,CAAC;AAgDpE;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,WAAW,CA4B5F"}
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 a deployed workspace both write through.
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 is
10
- * (`workspaceSetDataset`). A `folder` is updated a piece at a time: e3 stores
11
- * a collection as pieces, each holding the entries between two keys, so only
12
- * the pieces holding a changed or removed file are read and cut again, and
13
- * every other piece is carried over as it is stored (`storeCollection`), never
14
- * read. However large the folder, a sync holds the pieces it changes and the
15
- * files it read. The new folder is then set as the input's value by e3's door
16
- * for a value the store holds (`datasetSetStored`), which checks its type,
17
- * holds the dataset's write lock and versions it as any input write.
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 { StringType, compareFor, decodeBeast2For, variant } from '@elaraai/east';
25
- import { DatasetSegments, datasetSetStored, storeCollection, workspaceGetDatasetHash, workspaceSetDataset, } from '@elaraai/e3-core';
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