@elaraai/e3-cloud-files 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.
- 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/README.md
CHANGED
|
@@ -9,8 +9,17 @@ changed, the same way on a local repository and on a deployed workspace.
|
|
|
9
9
|
- **Your data stays in the bucket.** Locally, a sync reads the bucket with
|
|
10
10
|
your own cloud credentials. The bytes land only in the local e3 repository,
|
|
11
11
|
never in your project.
|
|
12
|
-
- **A file arrives as it is**, a `Blob
|
|
13
|
-
JSON, as CSV, or as text.
|
|
12
|
+
- **A file arrives as it is**, a `Blob`, and a task decodes it in East: as
|
|
13
|
+
East's JSON, as CSV, or as text. Or, named with a **type**, a beast2 file
|
|
14
|
+
arrives as the value it holds, an input of that type with no task in
|
|
15
|
+
between, a collection of any size.
|
|
16
|
+
- **Records are seeded and fed from files.** A record's mutation may be fed
|
|
17
|
+
by files: each new file is the mutation's one argument, applied once.
|
|
18
|
+
(A record seeded from a file, `upload.record`, lands with the next e3
|
|
19
|
+
release: see below.)
|
|
20
|
+
- **Push from disk.** `e3-cloud files push` puts files from your machine
|
|
21
|
+
into a source and takes them in, with your e3 login alone where the source
|
|
22
|
+
is the deployment's own bucket.
|
|
14
23
|
- **Nothing polls.** A sync runs when someone asks for it, and reads only the
|
|
15
24
|
files that changed since the last one.
|
|
16
25
|
|
|
@@ -54,9 +63,12 @@ export default Files.manifest([upload]);
|
|
|
54
63
|
| Shape | Selects | The input's type | Largest file |
|
|
55
64
|
|---|---|---|---|
|
|
56
65
|
| `file(input, { key })` | One key | `Blob` | 1 GiB less 64 KiB |
|
|
66
|
+
| `file(input, { key, type })` | One key, a beast2 file of `type` | `type` | 1 GiB for a value that is no collection; a collection any size (a segment at most 64 MiB) |
|
|
57
67
|
| `latest(input, { prefix, include? })` | The greatest key under the prefix that matches | `Blob` | 1 GiB less 64 KiB |
|
|
68
|
+
| `latest(input, { prefix, include?, type })` | The greatest key, a beast2 file of `type` | `type` | as a typed `file` |
|
|
58
69
|
| `folder(input, { prefix, include? })` | Every key under the prefix that matches, by its path under the prefix | `Dict<String, Blob>` | 64 MiB less 64 KiB each |
|
|
59
70
|
| `listing(input, { prefix, include? })` | The same keys' size, time and ETag, without their bytes | `Dict<String, Struct{size, modified, etag}>` | none |
|
|
71
|
+
| `mutation(def, { key } \| { prefix, include? })` | One key, or every file under a prefix its glob matches, each new one in key order: a beast2 file of the mutation's one argument's type | (the mutation's, as declared) | 1 GiB |
|
|
60
72
|
|
|
61
73
|
- **Each shape returns an ordinary `e3.input`.** It has no default, so it
|
|
62
74
|
stays unassigned until a sync sets it. A task takes it like any input, and
|
|
@@ -72,8 +84,60 @@ export default Files.manifest([upload]);
|
|
|
72
84
|
subfolders' too, their paths naming the subfolder (`2026/10/a.json`).
|
|
73
85
|
- **Mistakes are refused as they are declared** (`FilesDeclarationError`, one
|
|
74
86
|
line per problem): a name e3 refuses, a key that isn't relative, a glob
|
|
75
|
-
that can't match,
|
|
76
|
-
shapes filling one
|
|
87
|
+
that can't match, a `patch` mutation or one of another arity than one, and
|
|
88
|
+
in `Files.manifest` two sources of one name or two shapes filling one
|
|
89
|
+
target.
|
|
90
|
+
|
|
91
|
+
## Typed files and fed records
|
|
92
|
+
|
|
93
|
+
A beast2 file already is an East value: the East SDK (`encodeBeast2For`,
|
|
94
|
+
`Beast2ElementWriter`), east-py, `e3 convert` and `e3 dataset get -f beast2`
|
|
95
|
+
all write it. A `file` or `latest` that names a `type` is an input of that
|
|
96
|
+
type, and the sync takes the file in as the value it holds, with no task in
|
|
97
|
+
between:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
// src/package.ts
|
|
101
|
+
export const Price = StructType({ sku: StringType, price: FloatType });
|
|
102
|
+
export const prices = upload.file('prices', { key: 'prices.beast2', type: ArrayType(Price) }); // an input of Array<Price>
|
|
103
|
+
export const stock = upload.latest('stock', { prefix: 'stock/', type: ArrayType(Stock) }); // the greatest key, typed
|
|
104
|
+
|
|
105
|
+
export const priceBook = e3.record('price_book', DictType(StringType, FloatType), new Map());
|
|
106
|
+
export const loadPrices = e3.mutation.reduce('load_prices', priceBook, East.function([PriceBook, ArrayType(Price)], PriceBook, …));
|
|
107
|
+
export const summary = e3.task('price_summary', [prices, priceBook], …); // used as any dataset
|
|
108
|
+
|
|
109
|
+
// src/files.ts
|
|
110
|
+
upload.mutation(loadPrices, { prefix: 'deltas/', include: '*.beast2' }); // each new delta file calls load_prices with its value
|
|
111
|
+
export default Files.manifest([upload]);
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
- **The header first.** The sync reads a typed file's beast2 header before
|
|
115
|
+
any other byte of it and refuses a file of another type (`type_mismatch`,
|
|
116
|
+
naming both types) or no beast2 (`not_beast2`); the input keeps its value
|
|
117
|
+
and the sync goes on. A deploy of the declarations checks the type against
|
|
118
|
+
the package's input, record or mutation too.
|
|
119
|
+
- **Any size.** A collection (an `Array`, a `Set`, a `Dict`) is taken in by
|
|
120
|
+
pieces, as e3 takes in any delivery, however large; in the cloud never
|
|
121
|
+
through a disk. A value that is no collection is held whole: at most 1 GiB.
|
|
122
|
+
A file whose segments are larger than beast2's 64 MiB is refused naming
|
|
123
|
+
the fix (write it again with a current writer).
|
|
124
|
+
- **The same bytes again change nothing.** A file re-uploaded unchanged (a
|
|
125
|
+
new ETag, the same SHA-256) gives the same value: nothing downstream runs.
|
|
126
|
+
- **A fed mutation** (`upload.mutation(def, …)`) is the package's own, a
|
|
127
|
+
`reduce` or an `edit` taking exactly one argument (a `patch` cannot be
|
|
128
|
+
fed). Every new file the mapping selects is applied, in key order, once by
|
|
129
|
+
its bytes' hash: a sync run again applies nothing twice, and the same bytes
|
|
130
|
+
under another name are applied no more; a file the mutation refuses
|
|
131
|
+
(`invalid`, `failed`, `timed_out`) is reported and tried again only once it
|
|
132
|
+
changes, and the files after it wait; a feed of a record with no state yet,
|
|
133
|
+
or one busy, waits for the next sync. Its commit reads in the record's
|
|
134
|
+
history as any mutation's, its argument the delivery.
|
|
135
|
+
- **A seeded record** (`upload.record(name, type, { key })`, a record pending
|
|
136
|
+
until its file arrives, which is its first state) needs the e3 release
|
|
137
|
+
that lets a record be declared with no initial value
|
|
138
|
+
(elaraai/east-workspace#1363): the declarations, the checks (`record`
|
|
139
|
+
targets, `$init` and `$seed` commits, `--reset`) and the sync's seeds are
|
|
140
|
+
in place, and the declaration itself lands with that release.
|
|
77
141
|
|
|
78
142
|
## Decoding in a task
|
|
79
143
|
|
|
@@ -138,13 +202,38 @@ e3 dataflow run ./repo dev
|
|
|
138
202
|
only each folder's n greatest keys, which caps what comes down to your
|
|
139
203
|
machine.
|
|
140
204
|
- **`files sync`** lists each source and compares every file with what its
|
|
141
|
-
|
|
142
|
-
writes only the
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
205
|
+
target took last, by ETag and size. It reads only what is new or changed,
|
|
206
|
+
writes only the targets whose files changed, and says what it did per
|
|
207
|
+
target: the rows taken in, the commit a feed made, what was refused and
|
|
208
|
+
why. `--target` syncs only those (an input's name, `record:<name>`,
|
|
209
|
+
`mutation:<record>.<name>`, split at its last dot, so a mutation whose
|
|
210
|
+
name holds a dot is named only through the API's structured targets);
|
|
211
|
+
`--reset <record>` takes a seeded record's
|
|
212
|
+
file as its state again whatever has mutated it since; `--dry-run` lists
|
|
213
|
+
what a sync would take and takes nothing. A sync doesn't run the dataflow:
|
|
214
|
+
`e3 dataflow run` does, and the sync prints the command when anything
|
|
215
|
+
changed.
|
|
216
|
+
- **`files push`** puts files from disk into a source and takes them in:
|
|
217
|
+
each file is matched to the mapping that takes it (a `file` by its name, a
|
|
218
|
+
`latest` under its prefix, a directory to the `folder`; or `--as <target>`
|
|
219
|
+
names one for every file), checked against the mapping's type and limit,
|
|
220
|
+
put where the source is bound (with your e3 login alone where that is the
|
|
221
|
+
deployment's own bucket: the platform signs the upload, bound to the
|
|
222
|
+
file's SHA-256; with your own credentials, `--profile`, elsewhere), then
|
|
223
|
+
one sync of what was put. A file the source holds already is `unchanged`;
|
|
224
|
+
`--dry-run` plans and puts nothing. Running it again puts only what is
|
|
225
|
+
not there.
|
|
226
|
+
- **`files status`** shows each mapping with its shape, its type, the file
|
|
227
|
+
it holds and the commit it made (or `pending` for a record not yet
|
|
228
|
+
seeded), where each source is bound, and the last sync.
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
$ e3-cloud files push https://e3.example.com/repos/acme dev ./data/prices.beast2 ./data/deltas/2026-10-19.beast2
|
|
232
|
+
upload s3://acme-files/acme/dev/
|
|
233
|
+
prices.beast2 → prices Array<Struct{sku, price}> 38 MiB put taken in 412,310 rows
|
|
234
|
+
2026-10-19.beast2 → mutation:price_book.load_prices load_prices 1 MiB put committed 9f2c1a0b7e3d
|
|
235
|
+
Next: e3 dataflow run https://e3.example.com/repos/acme dev
|
|
236
|
+
```
|
|
148
237
|
|
|
149
238
|
A sync is safe to repeat and to stop. Each input is remembered only once it
|
|
150
239
|
is written, so a sync that stops part way takes the rest next time.
|
|
@@ -229,8 +318,11 @@ is read. So a sync holds one batch of files and the pieces it changes,
|
|
|
229
318
|
however large the folder; the first sync of 6 GB of files holds 256 MiB at a
|
|
230
319
|
time, and a sync stopped part way takes the rest next time.
|
|
231
320
|
|
|
232
|
-
A `file` or `latest` input is held whole while it is written: a
|
|
233
|
-
needs some 3 GiB of memory.
|
|
321
|
+
A `file` or `latest` input with no type is held whole while it is written: a
|
|
322
|
+
1 GiB file needs some 3 GiB of memory. A typed collection is never held
|
|
323
|
+
whole: in the cloud it is staged in the data bucket as it streams and taken
|
|
324
|
+
in by intake units on the sandbox functions; locally it is streamed to the
|
|
325
|
+
repository's own scratch and taken in by e3's door for a file.
|
|
234
326
|
|
|
235
327
|
### On a local repository
|
|
236
328
|
|
|
@@ -248,8 +340,11 @@ and profile it was: `aws sso login --profile <name>` renews it.
|
|
|
248
340
|
|
|
249
341
|
`@elaraai/e3-cloud-files/check` holds the checks without the authoring:
|
|
250
342
|
|
|
251
|
-
- `checkFiles(manifest,
|
|
252
|
-
(`
|
|
343
|
+
- `checkFiles(manifest, surface)`: the declarations against a package's
|
|
344
|
+
inputs, records and mutations (`readPackageSurface(read, packageHash)`
|
|
345
|
+
reads them from its objects): an input writable and of the mapping's type,
|
|
346
|
+
a record of the file's type, a mutation of the record taking one argument
|
|
347
|
+
of the file's type;
|
|
253
348
|
- `checkBindings(manifest, bindings, policy)`: bindings against the
|
|
254
349
|
declarations, and in the cloud against the sources the deployment grants.
|
|
255
350
|
|
|
@@ -260,13 +355,23 @@ rendered as one line by `filesProblemText`.
|
|
|
260
355
|
|
|
261
356
|
In `@elaraai/e3-cloud-types`, re-exported here:
|
|
262
357
|
|
|
263
|
-
- `FilesManifestType`: the sources' names, and each mapping's
|
|
264
|
-
|
|
358
|
+
- `FilesManifestType`: the sources' names, and each mapping's target
|
|
359
|
+
(`FilesTargetType`: `input`, `record` or `mutation`), source and shape
|
|
360
|
+
(`FilesShapeType`: `file` and `latest`, each with an optional `type`;
|
|
361
|
+
`folder`, `listing`).
|
|
265
362
|
- `FilesBindingType`: a source's URL, region, and, locally, profile and
|
|
266
363
|
`latest`.
|
|
267
|
-
- `FilesSyncReportType`: per
|
|
268
|
-
unchanged and refused
|
|
269
|
-
|
|
364
|
+
- `FilesSyncReportType`: per target, the files new, changed, removed,
|
|
365
|
+
unchanged and refused (`FilesRefusalReasonType`: `too_large`,
|
|
366
|
+
`segment_too_large`, `unreadable`, `not_beast2`, `type_mismatch`,
|
|
367
|
+
`mutation`, `missing`), the bytes taken in, whether it was written, the
|
|
368
|
+
typed value taken (`FilesTakenType`: its rows), the commit a seed or feed
|
|
369
|
+
made, and why a seed was kept or a feed waits.
|
|
370
|
+
- `FilesStatusType` (each mapping with what it holds and whether its record
|
|
371
|
+
is pending), `FilesProblemType`, the push's `FilesPushRequestType` and
|
|
372
|
+
`FilesPushPlanType`.
|
|
373
|
+
- Values stored by the first version (mappings by input) are read through
|
|
374
|
+
`@elaraai/e3-cloud-files/compat`, which lifts them.
|
|
270
375
|
- The inputs' types: `FilesFileType` (`Blob`), `FilesFolderType`
|
|
271
376
|
(`Dict<String, Blob>`), `FilesListingType`.
|
|
272
377
|
|
|
@@ -276,10 +381,11 @@ In `@elaraai/e3-cloud-types`, re-exported here:
|
|
|
276
381
|
|---|---|
|
|
277
382
|
| `@elaraai/e3-cloud-files` | Authoring: `Files.source`, the shapes, `Files.manifest`, and the types |
|
|
278
383
|
| `@elaraai/e3-cloud-files/check` | The checks, without the authoring |
|
|
279
|
-
| `@elaraai/e3-cloud-files/sync` | The sync (`syncFiles`) over what reads, writes and remembers, and
|
|
280
|
-
| `@elaraai/e3-cloud-files/storage` | The sync's writer over an e3 store (needs `@elaraai/e3-core`) |
|
|
384
|
+
| `@elaraai/e3-cloud-files/sync` | The sync (`syncFiles`) over what reads, writes and remembers, the push's plan (`planPush`), and the ports in memory for tests |
|
|
385
|
+
| `@elaraai/e3-cloud-files/storage` | The sync's writer over an e3 store, and the cloud's over a staging (needs `@elaraai/e3-core`) |
|
|
281
386
|
| `@elaraai/e3-cloud-files/s3` | The sync's reader of a bucket (needs `@aws-sdk/client-s3`) |
|
|
282
|
-
| `@elaraai/e3-cloud-files/
|
|
387
|
+
| `@elaraai/e3-cloud-files/compat` | The first version's stored types, and their lift into the current ones |
|
|
388
|
+
| `@elaraai/e3-cloud-files/examples/*` | The worked examples (`orders`, `typed`) and the decoding examples (`decode`) |
|
|
283
389
|
|
|
284
390
|
## License
|
|
285
391
|
|
package/dist/src/authoring.d.ts
CHANGED
|
@@ -3,29 +3,42 @@
|
|
|
3
3
|
* Licensed under BSL 1.1. See LICENSE for details.
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
|
-
* Declaring which inputs come from files
|
|
6
|
+
* Declaring which inputs come from files, and which records are seeded or
|
|
7
|
+
* fed from them.
|
|
7
8
|
*
|
|
8
9
|
* ```ts
|
|
9
10
|
* export const upload = Files.source('upload');
|
|
10
|
-
* export const rates = upload.file('rates', { key: 'rates.json' });
|
|
11
|
-
* export const
|
|
12
|
-
* export const
|
|
11
|
+
* export const rates = upload.file('rates', { key: 'rates.json' }); // a Blob
|
|
12
|
+
* export const orders = upload.file('orders', { key: 'orders.beast2', type: ArrayType(Order) }); // an Array<Order>
|
|
13
|
+
* export const stock = upload.latest('stock', { prefix: 'stock/', type: ArrayType(Stock) });
|
|
14
|
+
* export const files = upload.folder('invoices', { prefix: 'invoices/', include: '*.pdf' });
|
|
15
|
+
* upload.mutation(loadPrices, { key: 'prices.beast2' }); // each new file calls loadPrices
|
|
13
16
|
* export default Files.manifest([upload]);
|
|
14
17
|
* ```
|
|
15
18
|
*
|
|
16
|
-
* Each declaration is an ordinary `e3.input` with no default, so the
|
|
17
|
-
* is plain e3 and tasks take these inputs as any other. A sync fills
|
|
18
|
-
* until the first, they are unassigned.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* Each input declaration is an ordinary `e3.input` with no default, so the
|
|
20
|
+
* package is plain e3 and tasks take these inputs as any other. A sync fills
|
|
21
|
+
* them; until the first, they are unassigned. A `file` or `latest` that
|
|
22
|
+
* names a `type` is an input of that type, filled with the beast2 value the
|
|
23
|
+
* file holds; one that names none is a `Blob`, filled with the file's
|
|
24
|
+
* bytes. A mutation declared here is the package's own (`e3.mutation.reduce`
|
|
25
|
+
* or `edit`, taking one argument): a sync calls it once with each new file's
|
|
26
|
+
* value. What each source's files fill is the {@link Files.manifest}, which
|
|
27
|
+
* `e3-cloud files deploy` deploys beside the package.
|
|
21
28
|
*
|
|
22
29
|
* @packageDocumentation
|
|
23
30
|
*/
|
|
24
|
-
import e3 from '@elaraai/e3';
|
|
31
|
+
import e3, { type MutationDef } from '@elaraai/e3';
|
|
25
32
|
import { type EastType } from '@elaraai/east';
|
|
26
33
|
import { FilesFileType, FilesFolderType, FilesListingType, type FilesManifest, type FilesMapping, type FilesProblem } from '@elaraai/e3-cloud-types';
|
|
27
|
-
/** The input a declaration makes: an `e3.input` of its
|
|
34
|
+
/** The input a declaration makes: an `e3.input` of its type, with no default. */
|
|
28
35
|
export type FilesInput<Name extends string, T extends EastType> = ReturnType<typeof e3.input<Name, T>>;
|
|
36
|
+
/** The mutation a feed declaration hands back: the package's own definition, as it was given. */
|
|
37
|
+
export type FilesMutation<M extends MutationDef> = M;
|
|
38
|
+
/** One file, by its key relative to where the workspace binds the source. */
|
|
39
|
+
export interface FilesKeySpec {
|
|
40
|
+
readonly key: string;
|
|
41
|
+
}
|
|
29
42
|
/** The selection of `latest`, `folder` and `listing`: a prefix, and a glob over the paths under it. */
|
|
30
43
|
export interface FilesSelection {
|
|
31
44
|
/** The folder, relative to where the workspace binds the source: `''` for all of it, else ending with `/` */
|
|
@@ -33,6 +46,14 @@ export interface FilesSelection {
|
|
|
33
46
|
/** A glob over paths relative to the prefix: `*` within a segment, `**` across segments, `?` one character, `[a-z]` one of a set, `{csv,json}` any of its alternatives */
|
|
34
47
|
readonly include?: string;
|
|
35
48
|
}
|
|
49
|
+
/** One file, typed: the beast2 value of `type` it holds. */
|
|
50
|
+
export interface FilesTypedKeySpec<T extends EastType> extends FilesKeySpec {
|
|
51
|
+
readonly type: T;
|
|
52
|
+
}
|
|
53
|
+
/** The newest file of a selection, typed. */
|
|
54
|
+
export interface FilesTypedSelection<T extends EastType> extends FilesSelection {
|
|
55
|
+
readonly type: T;
|
|
56
|
+
}
|
|
36
57
|
/** Thrown when declarations are not well-formed, naming every problem. */
|
|
37
58
|
export declare class FilesDeclarationError extends Error {
|
|
38
59
|
/** Every problem */
|
|
@@ -41,7 +62,7 @@ export declare class FilesDeclarationError extends Error {
|
|
|
41
62
|
}
|
|
42
63
|
/**
|
|
43
64
|
* A source of files: a name each workspace binds to a bucket and a prefix,
|
|
44
|
-
* and the inputs its files fill.
|
|
65
|
+
* and the inputs, records and mutations its files fill.
|
|
45
66
|
*/
|
|
46
67
|
export declare class FilesSource {
|
|
47
68
|
#private;
|
|
@@ -49,27 +70,30 @@ export declare class FilesSource {
|
|
|
49
70
|
readonly name: string;
|
|
50
71
|
/** @internal Use {@link Files.source}. */
|
|
51
72
|
constructor(name: string);
|
|
52
|
-
/** The
|
|
73
|
+
/** The targets this source fills, in the order they were declared. */
|
|
53
74
|
get mappings(): readonly FilesMapping[];
|
|
54
75
|
/**
|
|
55
|
-
* One file
|
|
76
|
+
* One file: the input `.inputs.<input>`. As a `Blob`, its bytes as they
|
|
77
|
+
* are; or, with `type`, as the beast2 value of that type the file holds
|
|
78
|
+
* (a collection of any size, taken in by pieces).
|
|
56
79
|
*
|
|
57
80
|
* @param input - The input's name
|
|
58
|
-
* @param spec - `key`: the file, relative to where the workspace binds the source
|
|
81
|
+
* @param spec - `key`: the file, relative to where the workspace binds the source; `type`: the value it holds
|
|
59
82
|
* @returns The input, for tasks to take
|
|
60
83
|
*/
|
|
61
|
-
file<Name extends string>(input: Name, spec:
|
|
62
|
-
|
|
63
|
-
}): FilesInput<Name, typeof FilesFileType>;
|
|
84
|
+
file<Name extends string>(input: Name, spec: FilesKeySpec): FilesInput<Name, typeof FilesFileType>;
|
|
85
|
+
file<Name extends string, T extends EastType>(input: Name, spec: FilesTypedKeySpec<T>): FilesInput<Name, T>;
|
|
64
86
|
/**
|
|
65
|
-
* The newest file under a prefix
|
|
66
|
-
* `include`, so files named by their time pick the latest.
|
|
87
|
+
* The newest file under a prefix: the greatest key that matches
|
|
88
|
+
* `include`, so files named by their time pick the latest. As a `Blob`,
|
|
89
|
+
* or, with `type`, as the value it holds.
|
|
67
90
|
*
|
|
68
91
|
* @param input - The input's name
|
|
69
|
-
* @param spec - The prefix,
|
|
92
|
+
* @param spec - The prefix, a glob over the paths under it, and the value's type
|
|
70
93
|
* @returns The input, for tasks to take
|
|
71
94
|
*/
|
|
72
95
|
latest<Name extends string>(input: Name, spec: FilesSelection): FilesInput<Name, typeof FilesFileType>;
|
|
96
|
+
latest<Name extends string, T extends EastType>(input: Name, spec: FilesTypedSelection<T>): FilesInput<Name, T>;
|
|
73
97
|
/**
|
|
74
98
|
* Every file under a prefix that matches `include`, as a
|
|
75
99
|
* `Dict<String, Blob>` keyed by each file's path relative to the prefix.
|
|
@@ -89,6 +113,19 @@ export declare class FilesSource {
|
|
|
89
113
|
* @returns The input, for tasks to take
|
|
90
114
|
*/
|
|
91
115
|
listing<Name extends string>(input: Name, spec: FilesSelection): FilesInput<Name, typeof FilesListingType>;
|
|
116
|
+
/**
|
|
117
|
+
* A record's mutation, fed: a sync calls it once with each new file's
|
|
118
|
+
* value, which must be of the mutation's one argument's type. The
|
|
119
|
+
* mutation is the package's own, a `reduce` or an `edit` taking exactly
|
|
120
|
+
* one argument; a `patch` mutation takes a patch, not a value, so it
|
|
121
|
+
* cannot be fed.
|
|
122
|
+
*
|
|
123
|
+
* @param def - The mutation: `e3.mutation.reduce(...)` or `e3.mutation.edit(...)`
|
|
124
|
+
* @param spec - One file (`key`), or the newest under a prefix (`prefix`, `include`)
|
|
125
|
+
* @returns The mutation, as given
|
|
126
|
+
* @throws {FilesDeclarationError} When the mutation takes other than one argument, or is a `patch`
|
|
127
|
+
*/
|
|
128
|
+
mutation<M extends MutationDef>(def: M, spec: FilesKeySpec | FilesSelection): FilesMutation<M>;
|
|
92
129
|
}
|
|
93
130
|
/** Declaring files. */
|
|
94
131
|
export declare const Files: {
|
|
@@ -96,18 +133,18 @@ export declare const Files: {
|
|
|
96
133
|
* A source of files, which each workspace binds to a bucket and a prefix.
|
|
97
134
|
*
|
|
98
135
|
* @param name - Its name, unique among a solution's sources
|
|
99
|
-
* @returns The source, whose methods declare
|
|
136
|
+
* @returns The source, whose methods declare what its files fill
|
|
100
137
|
* @throws {FilesDeclarationError} When the name is one e3 refuses
|
|
101
138
|
*/
|
|
102
139
|
source(name: string): FilesSource;
|
|
103
140
|
/**
|
|
104
141
|
* The declarations `e3-cloud files deploy` deploys: the sources, and the
|
|
105
|
-
* inputs their files fill.
|
|
142
|
+
* inputs, records and mutations their files fill.
|
|
106
143
|
*
|
|
107
144
|
* @param sources - Every source
|
|
108
145
|
* @returns The manifest, an East value of `FilesManifestType`
|
|
109
146
|
* @throws {FilesDeclarationError} When two sources share a name, or two
|
|
110
|
-
* mappings fill one
|
|
147
|
+
* mappings fill one target
|
|
111
148
|
*/
|
|
112
149
|
manifest(sources: readonly FilesSource[]): FilesManifest;
|
|
113
150
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authoring.d.ts","sourceRoot":"","sources":["../../src/authoring.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH
|
|
1
|
+
{"version":3,"file":"authoring.d.ts","sourceRoot":"","sources":["../../src/authoring.ts"],"names":[],"mappings":"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"}
|
package/dist/src/authoring.js
CHANGED
|
@@ -3,28 +3,35 @@
|
|
|
3
3
|
* Licensed under BSL 1.1. See LICENSE for details.
|
|
4
4
|
*/
|
|
5
5
|
/**
|
|
6
|
-
* Declaring which inputs come from files
|
|
6
|
+
* Declaring which inputs come from files, and which records are seeded or
|
|
7
|
+
* fed from them.
|
|
7
8
|
*
|
|
8
9
|
* ```ts
|
|
9
10
|
* export const upload = Files.source('upload');
|
|
10
|
-
* export const rates = upload.file('rates', { key: 'rates.json' });
|
|
11
|
-
* export const
|
|
12
|
-
* export const
|
|
11
|
+
* export const rates = upload.file('rates', { key: 'rates.json' }); // a Blob
|
|
12
|
+
* export const orders = upload.file('orders', { key: 'orders.beast2', type: ArrayType(Order) }); // an Array<Order>
|
|
13
|
+
* export const stock = upload.latest('stock', { prefix: 'stock/', type: ArrayType(Stock) });
|
|
14
|
+
* export const files = upload.folder('invoices', { prefix: 'invoices/', include: '*.pdf' });
|
|
15
|
+
* upload.mutation(loadPrices, { key: 'prices.beast2' }); // each new file calls loadPrices
|
|
13
16
|
* export default Files.manifest([upload]);
|
|
14
17
|
* ```
|
|
15
18
|
*
|
|
16
|
-
* Each declaration is an ordinary `e3.input` with no default, so the
|
|
17
|
-
* is plain e3 and tasks take these inputs as any other. A sync fills
|
|
18
|
-
* until the first, they are unassigned.
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* Each input declaration is an ordinary `e3.input` with no default, so the
|
|
20
|
+
* package is plain e3 and tasks take these inputs as any other. A sync fills
|
|
21
|
+
* them; until the first, they are unassigned. A `file` or `latest` that
|
|
22
|
+
* names a `type` is an input of that type, filled with the beast2 value the
|
|
23
|
+
* file holds; one that names none is a `Blob`, filled with the file's
|
|
24
|
+
* bytes. A mutation declared here is the package's own (`e3.mutation.reduce`
|
|
25
|
+
* or `edit`, taking one argument): a sync calls it once with each new file's
|
|
26
|
+
* value. What each source's files fill is the {@link Files.manifest}, which
|
|
27
|
+
* `e3-cloud files deploy` deploys beside the package.
|
|
21
28
|
*
|
|
22
29
|
* @packageDocumentation
|
|
23
30
|
*/
|
|
24
31
|
import e3 from '@elaraai/e3';
|
|
25
|
-
import { none, some, variant } from '@elaraai/east';
|
|
32
|
+
import { none, some, toEastTypeValue, variant } from '@elaraai/east';
|
|
26
33
|
import { nameProblem } from '@elaraai/e3-types';
|
|
27
|
-
import { FilesFileType, FilesFolderType, FilesListingType, filesProblemText, } from '@elaraai/e3-cloud-types';
|
|
34
|
+
import { FilesFileType, FilesFolderType, FilesListingType, filesProblemText, sameFilesTarget, } from '@elaraai/e3-cloud-types';
|
|
28
35
|
import { includeProblem, keyProblem, prefixProblem } from './paths.js';
|
|
29
36
|
/** Thrown when declarations are not well-formed, naming every problem. */
|
|
30
37
|
export class FilesDeclarationError extends Error {
|
|
@@ -46,16 +53,21 @@ function nameProblems(kind, name) {
|
|
|
46
53
|
const reason = nameProblem('workspace', name);
|
|
47
54
|
return reason === null ? [] : [variant('invalid_name', { kind, name, reason })];
|
|
48
55
|
}
|
|
56
|
+
/** A key's problems. */
|
|
57
|
+
function keyProblems(target, key) {
|
|
58
|
+
const reason = keyProblem(key);
|
|
59
|
+
return reason === null ? [] : [variant('invalid_key', { target, key, reason })];
|
|
60
|
+
}
|
|
49
61
|
/** A selection's problems. */
|
|
50
|
-
function selectionProblems(
|
|
62
|
+
function selectionProblems(target, selection) {
|
|
51
63
|
const problems = [];
|
|
52
64
|
const prefix = prefixProblem(selection.prefix);
|
|
53
65
|
if (prefix !== null)
|
|
54
|
-
problems.push(variant('invalid_key', {
|
|
66
|
+
problems.push(variant('invalid_key', { target, key: selection.prefix, reason: prefix }));
|
|
55
67
|
if (selection.include !== undefined) {
|
|
56
68
|
const include = includeProblem(selection.include);
|
|
57
69
|
if (include !== null)
|
|
58
|
-
problems.push(variant('invalid_include', {
|
|
70
|
+
problems.push(variant('invalid_include', { target, include: selection.include, reason: include }));
|
|
59
71
|
}
|
|
60
72
|
return problems;
|
|
61
73
|
}
|
|
@@ -63,9 +75,23 @@ function selectionProblems(input, selection) {
|
|
|
63
75
|
function selected(selection) {
|
|
64
76
|
return { prefix: selection.prefix, include: selection.include === undefined ? none : some(selection.include) };
|
|
65
77
|
}
|
|
78
|
+
/** Whether a spec names one file, rather than a selection. */
|
|
79
|
+
function isKeySpec(spec) {
|
|
80
|
+
return 'key' in spec;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* The shape of a spec that selects one file, typed: `file` or `latest`, with
|
|
84
|
+
* the type, and its problems.
|
|
85
|
+
*/
|
|
86
|
+
function oneFileShape(target, spec, type) {
|
|
87
|
+
const typeValue = some(toEastTypeValue(type));
|
|
88
|
+
if (isKeySpec(spec))
|
|
89
|
+
return { shape: variant('file', { key: spec.key, type: typeValue }), problems: keyProblems(target, spec.key) };
|
|
90
|
+
return { shape: variant('latest', { ...selected(spec), type: typeValue }), problems: selectionProblems(target, spec) };
|
|
91
|
+
}
|
|
66
92
|
/**
|
|
67
93
|
* A source of files: a name each workspace binds to a bucket and a prefix,
|
|
68
|
-
* and the inputs its files fill.
|
|
94
|
+
* and the inputs, records and mutations its files fill.
|
|
69
95
|
*/
|
|
70
96
|
export class FilesSource {
|
|
71
97
|
/** The source's name */
|
|
@@ -76,38 +102,31 @@ export class FilesSource {
|
|
|
76
102
|
refuse(nameProblems('source', name));
|
|
77
103
|
this.name = name;
|
|
78
104
|
}
|
|
79
|
-
/** The
|
|
105
|
+
/** The targets this source fills, in the order they were declared. */
|
|
80
106
|
get mappings() {
|
|
81
107
|
return this.#mappings;
|
|
82
108
|
}
|
|
83
|
-
/** Records a mapping
|
|
84
|
-
#declare(
|
|
85
|
-
refuse(
|
|
86
|
-
this.#mappings.push({
|
|
109
|
+
/** Records a mapping, refusing its problems first. */
|
|
110
|
+
#declare(target, shape, problems) {
|
|
111
|
+
refuse(problems);
|
|
112
|
+
this.#mappings.push({ target, source: this.name, shape });
|
|
113
|
+
}
|
|
114
|
+
/** Records an input's mapping and makes its input. */
|
|
115
|
+
#input(input, type, shape, problems) {
|
|
116
|
+
this.#declare(variant('input', input), shape, [...nameProblems('input', input), ...problems]);
|
|
87
117
|
return e3.input(input, type);
|
|
88
118
|
}
|
|
89
|
-
/**
|
|
90
|
-
* One file, as a `Blob`: the input `.inputs.<input>`.
|
|
91
|
-
*
|
|
92
|
-
* @param input - The input's name
|
|
93
|
-
* @param spec - `key`: the file, relative to where the workspace binds the source
|
|
94
|
-
* @returns The input, for tasks to take
|
|
95
|
-
*/
|
|
96
119
|
file(input, spec) {
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
|
|
120
|
+
const target = variant('input', input);
|
|
121
|
+
const type = 'type' in spec ? spec.type : FilesFileType;
|
|
122
|
+
const shape = variant('file', { key: spec.key, type: 'type' in spec ? some(toEastTypeValue(spec.type)) : none });
|
|
123
|
+
return this.#input(input, type, shape, keyProblems(target, spec.key));
|
|
100
124
|
}
|
|
101
|
-
/**
|
|
102
|
-
* The newest file under a prefix, as a `Blob`: the greatest key that matches
|
|
103
|
-
* `include`, so files named by their time pick the latest.
|
|
104
|
-
*
|
|
105
|
-
* @param input - The input's name
|
|
106
|
-
* @param spec - The prefix, and a glob over the paths under it
|
|
107
|
-
* @returns The input, for tasks to take
|
|
108
|
-
*/
|
|
109
125
|
latest(input, spec) {
|
|
110
|
-
|
|
126
|
+
const target = variant('input', input);
|
|
127
|
+
const type = 'type' in spec ? spec.type : FilesFileType;
|
|
128
|
+
const shape = variant('latest', { ...selected(spec), type: 'type' in spec ? some(toEastTypeValue(spec.type)) : none });
|
|
129
|
+
return this.#input(input, type, shape, selectionProblems(target, spec));
|
|
111
130
|
}
|
|
112
131
|
/**
|
|
113
132
|
* Every file under a prefix that matches `include`, as a
|
|
@@ -118,7 +137,7 @@ export class FilesSource {
|
|
|
118
137
|
* @returns The input, for tasks to take
|
|
119
138
|
*/
|
|
120
139
|
folder(input, spec) {
|
|
121
|
-
return this.#
|
|
140
|
+
return this.#input(input, FilesFolderType, variant('folder', selected(spec)), selectionProblems(variant('input', input), spec));
|
|
122
141
|
}
|
|
123
142
|
/**
|
|
124
143
|
* The files under a prefix that match `include`, without their bytes: each
|
|
@@ -130,7 +149,32 @@ export class FilesSource {
|
|
|
130
149
|
* @returns The input, for tasks to take
|
|
131
150
|
*/
|
|
132
151
|
listing(input, spec) {
|
|
133
|
-
return this.#
|
|
152
|
+
return this.#input(input, FilesListingType, variant('listing', selected(spec)), selectionProblems(variant('input', input), spec));
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* A record's mutation, fed: a sync calls it once with each new file's
|
|
156
|
+
* value, which must be of the mutation's one argument's type. The
|
|
157
|
+
* mutation is the package's own, a `reduce` or an `edit` taking exactly
|
|
158
|
+
* one argument; a `patch` mutation takes a patch, not a value, so it
|
|
159
|
+
* cannot be fed.
|
|
160
|
+
*
|
|
161
|
+
* @param def - The mutation: `e3.mutation.reduce(...)` or `e3.mutation.edit(...)`
|
|
162
|
+
* @param spec - One file (`key`), or the newest under a prefix (`prefix`, `include`)
|
|
163
|
+
* @returns The mutation, as given
|
|
164
|
+
* @throws {FilesDeclarationError} When the mutation takes other than one argument, or is a `patch`
|
|
165
|
+
*/
|
|
166
|
+
mutation(def, spec) {
|
|
167
|
+
const record = def.record.name;
|
|
168
|
+
const target = variant('mutation', { record, mutation: def.name });
|
|
169
|
+
const problems = [...nameProblems('record', record), ...nameProblems('mutation', def.name)];
|
|
170
|
+
if (def.form === 'patch')
|
|
171
|
+
problems.push(variant('mutation_patch', { record, mutation: def.name }));
|
|
172
|
+
else if (def.argTypes.length !== 1)
|
|
173
|
+
problems.push(variant('mutation_arity', { record, mutation: def.name, arity: BigInt(def.argTypes.length) }));
|
|
174
|
+
refuse(problems);
|
|
175
|
+
const { shape, problems: specProblems } = oneFileShape(target, spec, def.argTypes[0]);
|
|
176
|
+
this.#declare(target, shape, specProblems);
|
|
177
|
+
return def;
|
|
134
178
|
}
|
|
135
179
|
}
|
|
136
180
|
/** Declaring files. */
|
|
@@ -139,7 +183,7 @@ export const Files = {
|
|
|
139
183
|
* A source of files, which each workspace binds to a bucket and a prefix.
|
|
140
184
|
*
|
|
141
185
|
* @param name - Its name, unique among a solution's sources
|
|
142
|
-
* @returns The source, whose methods declare
|
|
186
|
+
* @returns The source, whose methods declare what its files fill
|
|
143
187
|
* @throws {FilesDeclarationError} When the name is one e3 refuses
|
|
144
188
|
*/
|
|
145
189
|
source(name) {
|
|
@@ -147,17 +191,17 @@ export const Files = {
|
|
|
147
191
|
},
|
|
148
192
|
/**
|
|
149
193
|
* The declarations `e3-cloud files deploy` deploys: the sources, and the
|
|
150
|
-
* inputs their files fill.
|
|
194
|
+
* inputs, records and mutations their files fill.
|
|
151
195
|
*
|
|
152
196
|
* @param sources - Every source
|
|
153
197
|
* @returns The manifest, an East value of `FilesManifestType`
|
|
154
198
|
* @throws {FilesDeclarationError} When two sources share a name, or two
|
|
155
|
-
* mappings fill one
|
|
199
|
+
* mappings fill one target
|
|
156
200
|
*/
|
|
157
201
|
manifest(sources) {
|
|
158
202
|
const problems = [];
|
|
159
203
|
const names = new Set();
|
|
160
|
-
const
|
|
204
|
+
const targets = [];
|
|
161
205
|
const mappings = [];
|
|
162
206
|
for (const source of sources) {
|
|
163
207
|
if (names.has(source.name)) {
|
|
@@ -165,9 +209,9 @@ export const Files = {
|
|
|
165
209
|
}
|
|
166
210
|
names.add(source.name);
|
|
167
211
|
for (const mapping of source.mappings) {
|
|
168
|
-
if (
|
|
169
|
-
problems.push(variant('
|
|
170
|
-
|
|
212
|
+
if (targets.some((t) => sameFilesTarget(t, mapping.target)))
|
|
213
|
+
problems.push(variant('duplicate_target', { target: mapping.target }));
|
|
214
|
+
targets.push(mapping.target);
|
|
171
215
|
mappings.push(mapping);
|
|
172
216
|
}
|
|
173
217
|
}
|