@elaraai/e3-cloud-types 1.0.94 → 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.
@@ -4,28 +4,47 @@
4
4
  */
5
5
  /**
6
6
  * Files: a solution's inputs that come from files in a bucket, as East
7
- * values.
7
+ * values; and a record's seed, and a record's mutation fed, from
8
+ * files.
8
9
  *
9
10
  * A solution declares, with `@elaraai/e3-cloud-files`, which of its inputs a
10
11
  * source's files fill: one file, the newest under a prefix, every file under
11
- * a prefix, or a listing of them ({@link FilesShapeType}). The declarations
12
- * are a {@link FilesManifestType}, deployed beside the package and checked
12
+ * a prefix, or a listing of them ({@link FilesShapeType}); a `file` or
13
+ * `latest` shape may name a **type**, and the file is then taken in as the
14
+ * beast2 value it holds rather than as a `Blob`. What a mapping fills is its
15
+ * **target** ({@link FilesTargetType}): an input, a record (the file is the
16
+ * record's state: its genesis, then a seed again), or a record's mutation
17
+ * (each new file is the mutation's one argument). The declarations are a
18
+ * {@link FilesManifestType}, deployed beside the package and checked
13
19
  * against it. Each workspace binds a source to where its files are
14
20
  * ({@link FilesBindingType}); a sync brings in what changed and answers a
15
- * {@link FilesSyncReportType}. The checks answer {@link FilesProblemType}s,
16
- * each rendered as a line by {@link filesProblemText}.
21
+ * {@link FilesSyncReportType}; a push ({@link FilesPushRequestType}) puts
22
+ * files from disk into a source and syncs their targets. The checks answer
23
+ * {@link FilesProblemType}s, each rendered as a line by
24
+ * {@link filesProblemText}.
17
25
  *
18
- * A file arrives as it is, a `Blob`: nothing here parses it.
26
+ * A file with no type arrives as it is, a `Blob`: nothing here parses it. A
27
+ * typed file is beast2, whose root type must be the mapping's.
28
+ *
29
+ * @remarks
30
+ * WIRE COMPAT: the manifest, the reports, the ledgers and a sync's job are
31
+ * stored (the platform's `FILES/`, `FILESJOB/` and `RELEASE/` items and its
32
+ * `files-sync/` objects; a local repository's `.e3-cloud/files/<ws>.json`).
33
+ * Their first version (a mapping by `input`, a refusal's reason a string) is
34
+ * read through
35
+ * `@elaraai/e3-cloud-files/compat`, which keeps the first version's types
36
+ * and lifts a value into these; every store writes these. The API's own
37
+ * request and response types ship with the client and CLI.
19
38
  */
20
- import { ArrayType, BlobType, BooleanType, DateTimeType, DictType, EastTypeType, IntegerType, NullType, OptionType, StringType, StructType, VariantType, printTypeValueSummary, } from '@elaraai/east';
39
+ import { ArrayType, BlobType, BooleanType, DateTimeType, DictType, EastTypeType, IntegerType, NullType, OptionType, StringType, StructType, VariantType, equalFor, printTypeValueSummary, variant, } from '@elaraai/east';
21
40
  /**
22
41
  * Room a file's value leaves in its frame or segment for what beast2 adds:
23
42
  * the lengths before the bytes and, in a folder, the file's path.
24
43
  */
25
44
  const FILES_ENCODING_ROOM_BYTES = 64 * 1024;
26
45
  /**
27
- * The largest file `file` and `latest` take: beast2's largest frame, 1 GiB,
28
- * less the room its encoding takes (1 GiB less 64 KiB).
46
+ * The largest file `file` and `latest` take as a `Blob`: beast2's largest
47
+ * frame, 1 GiB, less the room its encoding takes (1 GiB less 64 KiB).
29
48
  */
30
49
  export const FILES_FILE_MAX_BYTES = 1024 * 1024 * 1024 - FILES_ENCODING_ROOM_BYTES;
31
50
  /**
@@ -33,6 +52,22 @@ export const FILES_FILE_MAX_BYTES = 1024 * 1024 * 1024 - FILES_ENCODING_ROOM_BYT
33
52
  * the room its path and lengths take (64 MiB less 64 KiB).
34
53
  */
35
54
  export const FILES_FOLDER_ENTRY_MAX_BYTES = 64 * 1024 * 1024 - FILES_ENCODING_ROOM_BYTES;
55
+ /**
56
+ * The largest typed file whose value is no collection, and the largest file
57
+ * a mutation is fed: beast2's largest frame, 1 GiB, which a sandbox holds
58
+ * whole. A typed collection has no limit but its segments'.
59
+ */
60
+ export const FILES_TYPED_VALUE_MAX_BYTES = 1024 * 1024 * 1024;
61
+ /**
62
+ * The largest segment of a typed collection: beast2's, 64 MiB. A file
63
+ * written with a larger one is refused, naming the fix (write it again
64
+ * with a current writer).
65
+ */
66
+ export const FILES_SEGMENT_MAX_BYTES = 64 * 1024 * 1024;
67
+ /** The most files one push plans. */
68
+ export const FILES_PUSH_MAX_FILES = 1000;
69
+ /** The part a push's multipart upload is cut in, and the most one PUT sends: 64 MiB. */
70
+ export const FILES_PUSH_PART_BYTES = 64 * 1024 * 1024;
36
71
  /** A file's row in a `listing` input. */
37
72
  export const FilesListingRowType = StructType({
38
73
  /** Its size in bytes */
@@ -42,7 +77,7 @@ export const FilesListingRowType = StructType({
42
77
  /** Its ETag: changes whenever its bytes do */
43
78
  etag: StringType,
44
79
  });
45
- /** The input type of a `file` or `latest` mapping: the file's bytes. */
80
+ /** The input type of a `file` or `latest` mapping with no type: the file's bytes. */
46
81
  export const FilesFileType = BlobType;
47
82
  /** The input type of a `folder` mapping: each file's path, relative to the prefix, to its bytes. */
48
83
  export const FilesFolderType = DictType(StringType, BlobType);
@@ -52,9 +87,10 @@ export const FilesListingType = DictType(StringType, FilesListingRowType);
52
87
  * What a mapping selects from its source, relative to where the workspace
53
88
  * binds the source.
54
89
  *
55
- * - `file`: one key, as a `Blob`.
90
+ * - `file`: one key. As a `Blob`, or, with `type`, as the beast2 value of
91
+ * that type the file holds.
56
92
  * - `latest`: the greatest key under a prefix that matches `include`, as a
57
- * `Blob`.
93
+ * `Blob` or, with `type`, as the value it holds.
58
94
  * - `folder`: every key under a prefix that matches `include`, as a
59
95
  * `Dict<String, Blob>` keyed by its path relative to the prefix.
60
96
  * - `listing`: the same keys' sizes, times and ETags, without their bytes.
@@ -63,22 +99,102 @@ export const FilesListingType = DictType(StringType, FilesListingRowType);
63
99
  * within one segment of the path, `**` across segments, `?` one character,
64
100
  * `[abc]`, `[a-z]` and `[!abc]` one character of a set, and `{csv,json}` any
65
101
  * of its alternatives, each a glob itself.
102
+ *
103
+ * A record's or a mutation's mapping selects one file (`file` or `latest`)
104
+ * and names its type: the record's state type, or the mutation's one
105
+ * argument's.
66
106
  */
67
107
  export const FilesShapeType = VariantType({
68
- file: StructType({ key: StringType }),
69
- latest: StructType({ prefix: StringType, include: OptionType(StringType) }),
108
+ file: StructType({ key: StringType, type: OptionType(EastTypeType) }),
109
+ latest: StructType({ prefix: StringType, include: OptionType(StringType), type: OptionType(EastTypeType) }),
70
110
  folder: StructType({ prefix: StringType, include: OptionType(StringType) }),
71
111
  listing: StructType({ prefix: StringType, include: OptionType(StringType) }),
72
112
  });
73
- /** One input a source's files fill. */
74
- export const FilesMappingType = StructType({
75
- /** The input's name: the dataset `.inputs.<input>` */
113
+ /**
114
+ * What a mapping fills.
115
+ *
116
+ * - `input`: the dataset `.inputs.<name>`.
117
+ * - `record`: the record `.records.<name>`, seeded: its file is its first
118
+ * state (its genesis), and a changed file its state again while nothing
119
+ * has mutated it since.
120
+ * - `mutation`: one of a record's mutations, fed: each new file is the
121
+ * mutation's one argument, applied once.
122
+ */
123
+ export const FilesTargetType = VariantType({
76
124
  input: StringType,
125
+ record: StringType,
126
+ mutation: StructType({ record: StringType, mutation: StringType }),
127
+ });
128
+ /** Whether two targets are the same. */
129
+ export const sameFilesTarget = equalFor(FilesTargetType);
130
+ /**
131
+ * A target as one text: `orders`, `record:customers`,
132
+ * `mutation:prices.load_prices`. What the CLI takes (`--as`, `--target`) and
133
+ * prints, and what {@link parseFilesTarget} reads back.
134
+ *
135
+ * @param target - The target
136
+ * @returns Its text
137
+ */
138
+ export function filesTargetText(target) {
139
+ switch (target.type) {
140
+ case 'input': return target.value;
141
+ case 'record': return `record:${target.value}`;
142
+ case 'mutation': return `mutation:${target.value.record}.${target.value.mutation}`;
143
+ }
144
+ }
145
+ /**
146
+ * A target, from its text ({@link filesTargetText}): a bare name is an
147
+ * input's. A mutation's text is split at its last dot (e3 lets a name hold
148
+ * one): a record's name may hold a dot, and a mutation whose name holds one
149
+ * cannot be named in this form, only as a structured target (a sync
150
+ * request's `targets`).
151
+ *
152
+ * @param text - `name`, `input:name`, `record:name` or `mutation:record.name`
153
+ * @returns The target, or null for text of another form
154
+ */
155
+ export function parseFilesTarget(text) {
156
+ const colon = text.indexOf(':');
157
+ if (colon < 0)
158
+ return text === '' ? null : variant('input', text);
159
+ const kind = text.slice(0, colon);
160
+ const rest = text.slice(colon + 1);
161
+ if (rest === '')
162
+ return null;
163
+ switch (kind) {
164
+ case 'input': return variant('input', rest);
165
+ case 'record': return variant('record', rest);
166
+ case 'mutation': {
167
+ const dot = rest.lastIndexOf('.');
168
+ if (dot <= 0 || dot === rest.length - 1)
169
+ return null;
170
+ return variant('mutation', { record: rest.slice(0, dot), mutation: rest.slice(dot + 1) });
171
+ }
172
+ default: return null;
173
+ }
174
+ }
175
+ /**
176
+ * A target in words: `input orders`, `record customers`,
177
+ * `mutation load_prices of customers`.
178
+ *
179
+ * @param target - The target
180
+ * @returns The words
181
+ */
182
+ export function filesTargetWords(target) {
183
+ switch (target.type) {
184
+ case 'input': return `input ${target.value}`;
185
+ case 'record': return `record ${target.value}`;
186
+ case 'mutation': return `mutation ${target.value.mutation} of ${target.value.record}`;
187
+ }
188
+ }
189
+ /** One target a source's files fill. */
190
+ export const FilesMappingType = StructType({
191
+ /** What the files fill */
192
+ target: FilesTargetType,
77
193
  /** The source's name */
78
194
  source: StringType,
79
195
  shape: FilesShapeType,
80
196
  });
81
- /** A solution's declarations: its sources, and the inputs they fill. */
197
+ /** A solution's declarations: its sources, and the targets they fill. */
82
198
  export const FilesManifestType = StructType({
83
199
  /** The sources' names, in the order they were declared */
84
200
  sources: ArrayType(StringType),
@@ -100,14 +216,52 @@ export const FilesBindingType = StructType({
100
216
  profile: OptionType(StringType),
101
217
  latest: OptionType(IntegerType),
102
218
  });
219
+ /**
220
+ * Why a sync did not take a file.
221
+ *
222
+ * - `too_large`: over its shape's limit, in bytes.
223
+ * - `segment_too_large`: a typed collection written with a segment over
224
+ * beast2's 64 MiB; written again with a current writer, it is taken.
225
+ * - `unreadable`: the source would not give it (archived, changed since it
226
+ * was listed, closed to the reader).
227
+ * - `not_beast2`: a typed mapping's file that is no beast2 container.
228
+ * - `type_mismatch`: a typed mapping's file holding a value of another
229
+ * type than the mapping's.
230
+ * - `mutation`: a feed the mutation did not commit: its outcome
231
+ * (`invalid`, `timed_out`, `failed`) and what it said; tried again once
232
+ * the file changes.
233
+ * - `missing`: the file the mapping names is not there.
234
+ * - `other`: a reason stored before these were told apart.
235
+ */
236
+ export const FilesRefusalReasonType = VariantType({
237
+ too_large: StructType({ size: IntegerType, limit: IntegerType }),
238
+ segment_too_large: StructType({ bytes: IntegerType, limit: IntegerType }),
239
+ unreadable: StringType,
240
+ not_beast2: StringType,
241
+ type_mismatch: StructType({ expected: EastTypeType, actual: EastTypeType }),
242
+ mutation: StructType({ outcome: StringType, message: StringType }),
243
+ missing: StringType,
244
+ other: StringType,
245
+ });
103
246
  /** A file a sync did not take, and why. */
104
247
  export const FilesRefusalType = StructType({
105
248
  key: StringType,
106
- reason: StringType,
249
+ reason: FilesRefusalReasonType,
250
+ });
251
+ /** A typed file a sync took in: the value it holds. */
252
+ export const FilesTakenType = StructType({
253
+ /** The file's key */
254
+ key: StringType,
255
+ /** The value's type: the mapping's */
256
+ type: EastTypeType,
257
+ /** The collection's rows, where the store counts them */
258
+ rows: OptionType(IntegerType),
259
+ /** `carried` whole, or `split` into pieces by intake units */
260
+ how: VariantType({ carried: NullType, split: NullType }),
107
261
  });
108
262
  /** What one sync did for one mapping. */
109
263
  export const FilesMappingReportType = StructType({
110
- input: StringType,
264
+ target: FilesTargetType,
111
265
  /** Files new since the last sync */
112
266
  added: IntegerType,
113
267
  /** Files whose bytes changed */
@@ -116,12 +270,20 @@ export const FilesMappingReportType = StructType({
116
270
  removed: IntegerType,
117
271
  /** Files as they were */
118
272
  unchanged: IntegerType,
119
- /** Files not taken: too large, or unreadable */
273
+ /** Files not taken, and why */
120
274
  refused: ArrayType(FilesRefusalType),
121
275
  /** The bytes taken in */
122
276
  bytes: IntegerType,
123
- /** Whether the input's value changed */
277
+ /** Whether the target's value changed */
124
278
  updated: BooleanType,
279
+ /** The typed value taken in, for a typed mapping that took its file */
280
+ taken: OptionType(FilesTakenType),
281
+ /** The commit a seed or a feed made: `$init`, `$seed`, or the mutation's */
282
+ commit: OptionType(StringType),
283
+ /** Why a seed was not applied: the record has been mutated since its seed */
284
+ kept: OptionType(StringType),
285
+ /** Why a feed waits: its record has no state yet; tried again by the next sync */
286
+ waiting: OptionType(StringType),
125
287
  });
126
288
  /** What one sync did. */
127
289
  export const FilesSyncReportType = StructType({
@@ -131,25 +293,66 @@ export const FilesSyncReportType = StructType({
131
293
  dryRun: BooleanType,
132
294
  mappings: ArrayType(FilesMappingReportType),
133
295
  });
296
+ /** What a target took of one file. */
297
+ export const FilesLedgerEntryType = StructType({
298
+ etag: StringType,
299
+ size: IntegerType,
300
+ /** The file's SHA-256, where the sync hashed it (a typed file, a push) */
301
+ sha256: OptionType(StringType),
302
+ /** When the file was last written, as its listing said */
303
+ modified: OptionType(DateTimeType),
304
+ });
134
305
  /**
135
- * What an input took last: each file's ETag and size by its key, the value the
136
- * sync wrote (its hash), and what it was taken under: the mapping's shape and
137
- * where its source was bound, as one text. A sync trusts it only while the
138
- * input still holds that value and the mapping and binding are as they were,
139
- * so a ledger a sync wrote under other declarations, or from elsewhere, is
306
+ * What a target took last: each file's ETag, size and hash by its key, the
307
+ * value the sync wrote (its hash) or the commit it made, and what it was
308
+ * taken under: the mapping's shape and where its source was bound, as one
309
+ * text. A sync trusts it only while the target still holds that value (or
310
+ * that commit is its head) and the mapping and binding are as they were, so
311
+ * a ledger a sync wrote under other declarations, or from elsewhere, is
140
312
  * never read against these. The CLI keeps it in a local repository's state
141
313
  * file, the platform in its store.
142
314
  */
143
315
  export const FilesLedgerType = StructType({
144
- entries: DictType(StringType, StructType({ etag: StringType, size: IntegerType })),
316
+ entries: DictType(StringType, FilesLedgerEntryType),
317
+ /** The value the sync wrote, its hash: an input's */
145
318
  value: OptionType(StringType),
146
319
  under: OptionType(StringType),
320
+ /** The commit the sync made: a record's seed, a mutation's last feed */
321
+ commit: OptionType(StringType),
322
+ /** The SHA-256 of a feed the mutation refused: not tried again until the file changes */
323
+ tried: OptionType(StringType),
324
+ /**
325
+ * A typed intake under way across rounds: the file (its key and ETag, so
326
+ * another file is never resumed as it), its SHA-256 once its bytes are
327
+ * placed in the store (the delivery its intake resumes), else where its
328
+ * staging stood (the platform's own progress, as text)
329
+ */
330
+ taking: OptionType(StructType({ key: StringType, etag: StringType, sha256: OptionType(StringType), staging: OptionType(StringType) })),
331
+ });
332
+ /** What a mapping holds, from its ledger: the file it took last. */
333
+ export const FilesHeldType = StructType({
334
+ key: StringType,
335
+ size: IntegerType,
336
+ modified: OptionType(DateTimeType),
337
+ sha256: OptionType(StringType),
338
+ /** The commit it made, for a record or a mutation */
339
+ commit: OptionType(StringType),
147
340
  });
148
- /** A workspace's Files: its declarations, its bindings and its last sync. */
341
+ /** A mapping's state: what it holds, and whether its record is pending. */
342
+ export const FilesMappingStatusType = StructType({
343
+ target: FilesTargetType,
344
+ /** The file it holds; none before its first sync, or for a folder or listing */
345
+ holds: OptionType(FilesHeldType),
346
+ /** A record mapping whose record has no state yet: seeded by the next sync */
347
+ pending: BooleanType,
348
+ });
349
+ /** A workspace's Files: its declarations, its bindings, its last sync, and what each mapping holds. */
149
350
  export const FilesStatusType = StructType({
150
351
  manifest: OptionType(FilesManifestType),
151
352
  bindings: ArrayType(FilesBindingType),
152
353
  lastSync: OptionType(FilesSyncReportType),
354
+ /** Each declared mapping's state, in the declarations' order; none without declarations */
355
+ mappings: ArrayType(FilesMappingStatusType),
153
356
  });
154
357
  /**
155
358
  * A bucket a deployment lets a repository's bindings point into, and the
@@ -168,7 +371,7 @@ export const FilesPackageType = StructType({
168
371
  });
169
372
  /** A deployed workspace's Files, as `GET …/files` answers them. */
170
373
  export const FilesWorkspaceType = StructType({
171
- /** Its declarations, its bindings and its last sync */
374
+ /** Its declarations, its bindings, its last sync and what each mapping holds */
172
375
  status: FilesStatusType,
173
376
  /** Where the repository's bindings may point */
174
377
  granted: ArrayType(FilesGrantType),
@@ -192,8 +395,10 @@ export const FilesDeployRequestType = StructType({
192
395
  export const FilesSyncRequestType = StructType({
193
396
  /** List what a sync would take, and take nothing */
194
397
  dryRun: BooleanType,
195
- /** Only these inputs; every declared input when none */
196
- inputs: OptionType(ArrayType(StringType)),
398
+ /** Only these targets; every declared target when none */
399
+ targets: OptionType(ArrayType(FilesTargetType)),
400
+ /** Records whose file is taken as their state again (`$seed`) whatever has mutated them since */
401
+ reset: ArrayType(StringType),
197
402
  });
198
403
  /** What a sync's request did: started a sync, its id; or found one under way, its id, and started nothing. */
199
404
  export const FilesSyncStartType = VariantType({
@@ -217,19 +422,104 @@ export const FilesSyncJobType = StructType({
217
422
  /** Who asked for it: their email, else their subject */
218
423
  by: StringType,
219
424
  dryRun: BooleanType,
220
- inputs: OptionType(ArrayType(StringType)),
425
+ targets: OptionType(ArrayType(FilesTargetType)),
426
+ reset: ArrayType(StringType),
221
427
  state: FilesSyncStateType,
222
428
  startedAt: DateTimeType,
223
429
  finishedAt: OptionType(DateTimeType),
224
- /** What it has taken, per input; whole once it has ended */
430
+ /** What it has taken, per target; whole once it has ended */
225
431
  report: OptionType(FilesSyncReportType),
226
432
  });
433
+ /** A file a push offers: its name, where it came from, its size, its hash and its beast2 type. */
434
+ export const FilesPushFileType = StructType({
435
+ /** Its name: a file's own, or its path relative to the directory it came from */
436
+ name: StringType,
437
+ /** The directory it came from, its own name, when a directory was pushed */
438
+ directory: OptionType(StringType),
439
+ size: IntegerType,
440
+ /** Its SHA-256, hex */
441
+ sha256: StringType,
442
+ /** The root type its beast2 header declares; none for a file that is no beast2 */
443
+ type: OptionType(EastTypeType),
444
+ });
445
+ /** A push's plan asked for (`POST …/files/push`): the files, and the target they are all for, if named. */
446
+ export const FilesPushRequestType = StructType({
447
+ files: ArrayType(FilesPushFileType),
448
+ /** The target every file is for ({@link filesTargetText}; a bare name when unique across kinds); none to match each by its name */
449
+ as: OptionType(StringType),
450
+ });
451
+ /**
452
+ * How a planned file is put: one presigned PUT bound to its SHA-256; a
453
+ * multipart upload the platform created, a presigned URL per part,
454
+ * completed by `POST …/files/push/complete`; or `external`, a bucket the
455
+ * platform does not write, which the client puts with its own credentials.
456
+ */
457
+ export const FilesPushUploadType = VariantType({
458
+ single: StructType({ url: StringType, headers: DictType(StringType, StringType) }),
459
+ multipart: StructType({
460
+ uploadId: StringType,
461
+ /** Each part's bytes but the last's */
462
+ partBytes: IntegerType,
463
+ parts: ArrayType(StructType({ number: IntegerType, url: StringType })),
464
+ }),
465
+ external: NullType,
466
+ });
467
+ /**
468
+ * Why a push does not put a file.
469
+ *
470
+ * - `no_mapping`: no mapping takes a file of its name.
471
+ * - `ambiguous`: several would; name one with `as`.
472
+ * - `unknown_target`: `as` names no mapping.
473
+ * - `shape_mismatch`: a directory for a mapping that takes one file, or a
474
+ * file for one that takes a folder (`directory`: which was offered).
475
+ * - `not_included`: a file the mapping's `include` glob leaves out, which
476
+ * no sync would take.
477
+ * - `not_latest`: a `latest` mapping whose source holds a greater key.
478
+ * - `type_mismatch`, `not_beast2`, `too_large`: as a sync refuses them.
479
+ */
480
+ export const FilesPushRefusalType = VariantType({
481
+ no_mapping: NullType,
482
+ ambiguous: ArrayType(FilesTargetType),
483
+ unknown_target: StringType,
484
+ shape_mismatch: StructType({ target: FilesTargetType, directory: BooleanType }),
485
+ not_included: StructType({ target: FilesTargetType, include: StringType }),
486
+ not_latest: StructType({ target: FilesTargetType, greatest: StringType }),
487
+ type_mismatch: StructType({ target: FilesTargetType, expected: EastTypeType, actual: EastTypeType }),
488
+ not_beast2: StructType({ target: FilesTargetType, expected: EastTypeType }),
489
+ too_large: StructType({ target: FilesTargetType, size: IntegerType, limit: IntegerType }),
490
+ });
491
+ /** What a push does with one file: puts it at a key, finds it there already, or refuses it. */
492
+ export const FilesPushOutcomeType = VariantType({
493
+ put: StructType({ target: FilesTargetType, source: StringType, key: StringType, upload: FilesPushUploadType }),
494
+ unchanged: StructType({ target: FilesTargetType, source: StringType, key: StringType }),
495
+ refused: FilesPushRefusalType,
496
+ });
497
+ /** A push's plan: each file's outcome, and the bindings the puts go to. */
498
+ export const FilesPushPlanType = StructType({
499
+ /** The bindings of the sources any file is put to */
500
+ bindings: ArrayType(FilesBindingType),
501
+ files: ArrayType(StructType({ name: StringType, directory: OptionType(StringType), outcome: FilesPushOutcomeType })),
502
+ });
503
+ /** The completion of a multipart upload a plan created (`POST …/files/push/complete`). */
504
+ export const FilesPushCompleteRequestType = StructType({
505
+ key: StringType,
506
+ uploadId: StringType,
507
+ parts: ArrayType(StructType({ number: IntegerType, etag: StringType })),
508
+ });
509
+ /** A completed upload: the object's ETag. */
510
+ export const FilesPushCompleteResponseType = StructType({ etag: StringType });
227
511
  /**
228
512
  * Why declarations do not fit a package, or a binding does not fit them.
229
513
  *
230
- * - Against the package: `input_missing`, `input_not_writable`, `input_type`.
231
- * - The declarations themselves: `duplicate_input` (two mappings fill one
232
- * input), `invalid_name` (a name e3 refuses), `invalid_key` (a key or
514
+ * - Against the package: `input_missing`, `input_not_writable`,
515
+ * `input_type`; `record_missing`, `record_type`; `mutation_missing`,
516
+ * `mutation_arity` (a fed mutation takes exactly one argument),
517
+ * `mutation_patch` (a `patch` mutation takes a patch, not a value),
518
+ * `mutation_type`.
519
+ * - The declarations themselves: `duplicate_target` (two mappings fill one
520
+ * target), `target_shape` (a record or mutation mapped to a folder or a
521
+ * listing), `target_untyped` (a record or mutation mapping naming no
522
+ * type), `invalid_name` (a name e3 refuses), `invalid_key` (a key or
233
523
  * prefix that is empty or not relative), `invalid_include` (a glob that
234
524
  * matches nothing well-formed).
235
525
  * - Bindings: `source_unknown` (bound, but not declared), `source_unbound`
@@ -237,7 +527,7 @@ export const FilesSyncJobType = StructType({
237
527
  * `not_granted` (the deployment grants no source holding the URL), and
238
528
  * `local_only` (`profile` or `latest`, on a deployed workspace).
239
529
  *
240
- * Types are compared exactly: `expected` is the shape's, `actual` the
530
+ * Types are compared exactly: `expected` is the mapping's, `actual` the
241
531
  * package's.
242
532
  *
243
533
  * @remarks
@@ -248,10 +538,18 @@ export const FilesProblemType = VariantType({
248
538
  input_missing: StructType({ input: StringType, expected: EastTypeType }),
249
539
  input_not_writable: StructType({ input: StringType }),
250
540
  input_type: StructType({ input: StringType, expected: EastTypeType, actual: EastTypeType }),
251
- duplicate_input: StructType({ input: StringType }),
541
+ record_missing: StructType({ record: StringType, expected: EastTypeType }),
542
+ record_type: StructType({ record: StringType, expected: EastTypeType, actual: EastTypeType }),
543
+ mutation_missing: StructType({ record: StringType, mutation: StringType }),
544
+ mutation_arity: StructType({ record: StringType, mutation: StringType, arity: IntegerType }),
545
+ mutation_patch: StructType({ record: StringType, mutation: StringType }),
546
+ mutation_type: StructType({ record: StringType, mutation: StringType, expected: EastTypeType, actual: EastTypeType }),
547
+ duplicate_target: StructType({ target: FilesTargetType }),
548
+ target_shape: StructType({ target: FilesTargetType }),
549
+ target_untyped: StructType({ target: FilesTargetType }),
252
550
  invalid_name: StructType({ kind: StringType, name: StringType, reason: StringType }),
253
- invalid_key: StructType({ input: StringType, key: StringType, reason: StringType }),
254
- invalid_include: StructType({ input: StringType, include: StringType, reason: StringType }),
551
+ invalid_key: StructType({ target: FilesTargetType, key: StringType, reason: StringType }),
552
+ invalid_include: StructType({ target: FilesTargetType, include: StringType, reason: StringType }),
255
553
  source_unknown: StructType({ source: StringType }),
256
554
  source_unbound: StructType({ source: StringType }),
257
555
  invalid_url: StructType({ source: StringType, url: StringType, reason: StringType }),
@@ -259,28 +557,59 @@ export const FilesProblemType = VariantType({
259
557
  local_only: StructType({ source: StringType, option: StringType }),
260
558
  });
261
559
  /**
262
- * What a deploy of declarations did: kept them, with the inputs whose
560
+ * What a deploy of declarations did: kept them, with the targets whose
263
561
  * declaration changed, which the next sync takes again whole; checked them
264
562
  * and kept nothing (a dry run); or refused them, naming every problem. Each
265
563
  * names the package the workspace runs, which they were checked against.
266
564
  */
267
565
  export const FilesDeployResponseType = VariantType({
268
- deployed: StructType({ package: FilesPackageType, retaken: ArrayType(StringType) }),
566
+ deployed: StructType({ package: FilesPackageType, retaken: ArrayType(FilesTargetType) }),
269
567
  checked: StructType({ package: FilesPackageType }),
270
568
  refused: StructType({ package: FilesPackageType, problems: ArrayType(FilesProblemType) }),
271
569
  });
272
570
  /**
273
- * What a bind did: kept the binding, with the inputs the next sync takes
571
+ * What a bind did: kept the binding, with the targets the next sync takes
274
572
  * again whole when it moved; or refused it, naming every problem.
275
573
  */
276
574
  export const FilesBindResponseType = VariantType({
277
- bound: StructType({ retaken: ArrayType(StringType) }),
575
+ bound: StructType({ retaken: ArrayType(FilesTargetType) }),
278
576
  refused: ArrayType(FilesProblemType),
279
577
  });
280
- /** A type, briefly, as the problems print it. */
281
- function typeWords(type) {
578
+ /**
579
+ * A type, briefly, as the problems and refusals print it.
580
+ *
581
+ * @param type - The type
582
+ * @returns Its summary, a few levels deep
583
+ */
584
+ export function filesTypeWords(type) {
282
585
  return printTypeValueSummary(type, 3, 4);
283
586
  }
587
+ /**
588
+ * A refusal's reason as words that follow the file's key.
589
+ *
590
+ * @param reason - The reason
591
+ * @returns The words
592
+ */
593
+ export function filesRefusalText(reason) {
594
+ switch (reason.type) {
595
+ case 'too_large':
596
+ return `is ${reason.value.size} bytes, over the ${reason.value.limit} its mapping takes`;
597
+ case 'segment_too_large':
598
+ return `holds a segment of ${reason.value.bytes} bytes, over beast2's ${reason.value.limit}: write it again with a current writer`;
599
+ case 'unreadable':
600
+ return reason.value;
601
+ case 'not_beast2':
602
+ return `is not a beast2 file (${reason.value})`;
603
+ case 'type_mismatch':
604
+ return `holds ${filesTypeWords(reason.value.actual)}; its mapping declares ${filesTypeWords(reason.value.expected)}`;
605
+ case 'mutation':
606
+ return `was not applied: the mutation answered ${reason.value.outcome}${reason.value.message === '' ? '' : ` (${reason.value.message})`}`;
607
+ case 'missing':
608
+ return reason.value;
609
+ case 'other':
610
+ return reason.value;
611
+ }
612
+ }
284
613
  /**
285
614
  * A problem as one line, which the CLI prints and an error's message carries.
286
615
  *
@@ -290,19 +619,35 @@ function typeWords(type) {
290
619
  export function filesProblemText(problem) {
291
620
  switch (problem.type) {
292
621
  case 'input_missing':
293
- return `input ${problem.value.input}: the package has no such input (a ${typeWords(problem.value.expected)} is declared)`;
622
+ return `input ${problem.value.input}: the package has no such input (a ${filesTypeWords(problem.value.expected)} is declared)`;
294
623
  case 'input_not_writable':
295
624
  return `input ${problem.value.input}: it isn't an input the package lets anything write, so no file can fill it`;
296
625
  case 'input_type':
297
- return `input ${problem.value.input}: the package declares ${typeWords(problem.value.actual)}, but its files fill a ${typeWords(problem.value.expected)}`;
298
- case 'duplicate_input':
299
- return `input ${problem.value.input}: more than one mapping fills it`;
626
+ return `input ${problem.value.input}: the package declares ${filesTypeWords(problem.value.actual)}, but its files fill a ${filesTypeWords(problem.value.expected)}`;
627
+ case 'record_missing':
628
+ return `record ${problem.value.record}: the package has no such record (a ${filesTypeWords(problem.value.expected)} is declared)`;
629
+ case 'record_type':
630
+ return `record ${problem.value.record}: the package declares its state ${filesTypeWords(problem.value.actual)}, but its file holds a ${filesTypeWords(problem.value.expected)}`;
631
+ case 'mutation_missing':
632
+ return `mutation ${problem.value.mutation} of ${problem.value.record}: the package has no such mutation on the record`;
633
+ case 'mutation_arity':
634
+ return `mutation ${problem.value.mutation} of ${problem.value.record}: it takes ${problem.value.arity} arguments, and a fed mutation takes exactly one`;
635
+ case 'mutation_patch':
636
+ return `mutation ${problem.value.mutation} of ${problem.value.record}: a patch mutation takes a patch, not a value, so no file can feed it`;
637
+ case 'mutation_type':
638
+ return `mutation ${problem.value.mutation} of ${problem.value.record}: its argument is ${filesTypeWords(problem.value.actual)}, but its file holds a ${filesTypeWords(problem.value.expected)}`;
639
+ case 'duplicate_target':
640
+ return `${filesTargetWords(problem.value.target)}: more than one mapping fills it`;
641
+ case 'target_shape':
642
+ return `${filesTargetWords(problem.value.target)}: a record or a mutation takes one file (file or latest), not a folder or a listing`;
643
+ case 'target_untyped':
644
+ return `${filesTargetWords(problem.value.target)}: its mapping names no type, and a record's or a mutation's file is a typed value`;
300
645
  case 'invalid_name':
301
646
  return `${problem.value.kind} ${problem.value.name}: ${problem.value.reason}`;
302
647
  case 'invalid_key':
303
- return `input ${problem.value.input}: '${problem.value.key}' ${problem.value.reason}`;
648
+ return `${filesTargetWords(problem.value.target)}: '${problem.value.key}' ${problem.value.reason}`;
304
649
  case 'invalid_include':
305
- return `input ${problem.value.input}: include '${problem.value.include}' ${problem.value.reason}`;
650
+ return `${filesTargetWords(problem.value.target)}: include '${problem.value.include}' ${problem.value.reason}`;
306
651
  case 'source_unknown':
307
652
  return `source ${problem.value.source}: the declarations have no such source`;
308
653
  case 'source_unbound':
@@ -315,4 +660,34 @@ export function filesProblemText(problem) {
315
660
  return `source ${problem.value.source}: ${problem.value.option} applies to a local repository only`;
316
661
  }
317
662
  }
663
+ /**
664
+ * A push's refusal as words that follow the file's name.
665
+ *
666
+ * @param refusal - The refusal
667
+ * @returns The words
668
+ */
669
+ export function filesPushRefusalText(refusal) {
670
+ switch (refusal.type) {
671
+ case 'no_mapping':
672
+ return 'no mapping takes a file of this name: name its target with --as';
673
+ case 'ambiguous':
674
+ return `several mappings would take it (${refusal.value.map(filesTargetText).join(', ')}): name one with --as`;
675
+ case 'unknown_target':
676
+ return `no mapping fills ${refusal.value}`;
677
+ case 'shape_mismatch':
678
+ return refusal.value.directory
679
+ ? `${filesTargetWords(refusal.value.target)} takes one file, not a directory`
680
+ : `${filesTargetWords(refusal.value.target)} takes a folder of files, not one file`;
681
+ case 'not_included':
682
+ return `${filesTargetWords(refusal.value.target)} takes only files matching '${refusal.value.include}', which this name does not`;
683
+ case 'not_latest':
684
+ return `${filesTargetWords(refusal.value.target)} takes the greatest key under its prefix, and ${refusal.value.greatest} is greater`;
685
+ case 'type_mismatch':
686
+ return `holds ${filesTypeWords(refusal.value.actual)}; ${filesTargetWords(refusal.value.target)} declares ${filesTypeWords(refusal.value.expected)}`;
687
+ case 'not_beast2':
688
+ return `is not a beast2 file, and ${filesTargetWords(refusal.value.target)} takes a ${filesTypeWords(refusal.value.expected)}`;
689
+ case 'too_large':
690
+ return `is ${refusal.value.size} bytes, over the ${refusal.value.limit} ${filesTargetWords(refusal.value.target)} takes`;
691
+ }
692
+ }
318
693
  //# sourceMappingURL=files-types.js.map