@toa.io/extensions.storages 1.0.0-alpha.34 → 1.0.0-alpha.340

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 (122) hide show
  1. package/package.json +43 -18
  2. package/readme.md +127 -47
  3. package/transpiled/Aspect.d.ts +8 -4
  4. package/transpiled/Aspect.js +12 -14
  5. package/transpiled/Aspect.js.map +1 -1
  6. package/transpiled/Entry.d.ts +14 -11
  7. package/transpiled/Entry.js +1 -2
  8. package/transpiled/Factory.d.ts +10 -2
  9. package/transpiled/Factory.js +52 -32
  10. package/transpiled/Factory.js.map +1 -1
  11. package/transpiled/Provider.d.ts +23 -19
  12. package/transpiled/Provider.js +18 -32
  13. package/transpiled/Provider.js.map +1 -1
  14. package/transpiled/Scanner.d.ts +10 -4
  15. package/transpiled/Scanner.js +38 -26
  16. package/transpiled/Scanner.js.map +1 -1
  17. package/transpiled/Secrets.d.ts +5 -0
  18. package/transpiled/Secrets.js +2 -0
  19. package/transpiled/Secrets.js.map +1 -0
  20. package/transpiled/Storage.d.ts +35 -22
  21. package/transpiled/Storage.js +95 -141
  22. package/transpiled/Storage.js.map +1 -1
  23. package/transpiled/errors.d.ts +15 -0
  24. package/transpiled/errors.js +12 -0
  25. package/transpiled/errors.js.map +1 -0
  26. package/transpiled/index.d.ts +4 -4
  27. package/transpiled/index.js +1 -9
  28. package/transpiled/index.js.map +1 -1
  29. package/transpiled/measurements.d.ts +3 -0
  30. package/transpiled/measurements.js +23 -0
  31. package/transpiled/measurements.js.map +1 -0
  32. package/transpiled/providers/Cloudinary.d.ts +55 -0
  33. package/transpiled/providers/Cloudinary.js +232 -0
  34. package/transpiled/providers/Cloudinary.js.map +1 -0
  35. package/transpiled/providers/Declaration.d.ts +9 -5
  36. package/transpiled/providers/Declaration.js +1 -2
  37. package/transpiled/providers/FileSystem.d.ts +16 -11
  38. package/transpiled/providers/FileSystem.js +64 -43
  39. package/transpiled/providers/FileSystem.js.map +1 -1
  40. package/transpiled/providers/S3.d.ts +18 -18
  41. package/transpiled/providers/S3.js +87 -122
  42. package/transpiled/providers/S3.js.map +1 -1
  43. package/transpiled/providers/Spaces.d.ts +12 -0
  44. package/transpiled/providers/Spaces.js +13 -0
  45. package/transpiled/providers/Spaces.js.map +1 -0
  46. package/transpiled/providers/Temporary.d.ts +2 -3
  47. package/transpiled/providers/Temporary.js +7 -11
  48. package/transpiled/providers/Temporary.js.map +1 -1
  49. package/transpiled/providers/Test.d.ts +4 -4
  50. package/transpiled/providers/Test.js +6 -12
  51. package/transpiled/providers/Test.js.map +1 -1
  52. package/transpiled/providers/index.d.ts +23 -11
  53. package/transpiled/providers/index.js +30 -14
  54. package/transpiled/providers/index.js.map +1 -1
  55. package/transpiled/providers/index.types.d.ts +5 -0
  56. package/transpiled/providers/index.types.js +2 -0
  57. package/transpiled/providers/index.types.js.map +1 -0
  58. package/transpiled/test/util.d.ts +113 -6
  59. package/transpiled/test/util.js +68 -15
  60. package/transpiled/test/util.js.map +1 -1
  61. package/schemas/annotation.cos.yaml +0 -10
  62. package/schemas/fs.cos.yaml +0 -9
  63. package/schemas/mem.cos.yaml +0 -6
  64. package/schemas/s3.cos.yaml +0 -16
  65. package/schemas/test.cos.yaml +0 -8
  66. package/schemas/tmp.cos.yaml +0 -9
  67. package/source/Annotation.ts +0 -39
  68. package/source/Aspect.ts +0 -25
  69. package/source/Entry.ts +0 -14
  70. package/source/Factory.ts +0 -68
  71. package/source/Provider.ts +0 -37
  72. package/source/Scanner.ts +0 -153
  73. package/source/Storage.test.ts +0 -472
  74. package/source/Storage.ts +0 -235
  75. package/source/deployment.ts +0 -57
  76. package/source/index.ts +0 -5
  77. package/source/manifest.ts +0 -8
  78. package/source/providers/Declaration.ts +0 -10
  79. package/source/providers/FileSystem.test.ts +0 -5
  80. package/source/providers/FileSystem.ts +0 -63
  81. package/source/providers/Memory.ts +0 -55
  82. package/source/providers/S3.test.ts +0 -134
  83. package/source/providers/S3.ts +0 -196
  84. package/source/providers/Temporary.ts +0 -16
  85. package/source/providers/Test.ts +0 -13
  86. package/source/providers/index.test.ts +0 -136
  87. package/source/providers/index.ts +0 -16
  88. package/source/providers/readme.md +0 -25
  89. package/source/schemas.test.ts +0 -58
  90. package/source/schemas.ts +0 -15
  91. package/source/test/.env.example +0 -1
  92. package/source/test/albert.jpg +0 -0
  93. package/source/test/empty.txt +0 -0
  94. package/source/test/lenna.ascii +0 -64
  95. package/source/test/lenna.png +0 -0
  96. package/source/test/sample.avi +0 -0
  97. package/source/test/sample.avif +0 -0
  98. package/source/test/sample.gif +0 -0
  99. package/source/test/sample.heic +0 -0
  100. package/source/test/sample.jpeg +0 -0
  101. package/source/test/sample.jxl +0 -0
  102. package/source/test/sample.svg +0 -4
  103. package/source/test/sample.wav +0 -0
  104. package/source/test/sample.webp +0 -0
  105. package/source/test/util.ts +0 -45
  106. package/transpiled/Annotation.d.ts +0 -3
  107. package/transpiled/Annotation.js +0 -57
  108. package/transpiled/Annotation.js.map +0 -1
  109. package/transpiled/deployment.d.ts +0 -5
  110. package/transpiled/deployment.js +0 -68
  111. package/transpiled/deployment.js.map +0 -1
  112. package/transpiled/manifest.d.ts +0 -1
  113. package/transpiled/manifest.js +0 -9
  114. package/transpiled/manifest.js.map +0 -1
  115. package/transpiled/providers/Memory.d.ts +0 -15
  116. package/transpiled/providers/Memory.js +0 -72
  117. package/transpiled/providers/Memory.js.map +0 -1
  118. package/transpiled/schemas.d.ts +0 -9
  119. package/transpiled/schemas.js +0 -14
  120. package/transpiled/schemas.js.map +0 -1
  121. package/transpiled/tsconfig.tsbuildinfo +0 -1
  122. package/tsconfig.json +0 -12
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@toa.io/extensions.storages",
3
- "version": "1.0.0-alpha.34",
3
+ "version": "1.0.0-alpha.340",
4
+ "type": "module",
4
5
  "description": "Toa Storages",
5
6
  "author": "temich <tema.gurtovoy@gmail.com>",
6
7
  "homepage": "https://github.com/toa-io/toa#readme",
@@ -16,27 +17,51 @@
16
17
  "publishConfig": {
17
18
  "access": "public"
18
19
  },
19
- "jest": {
20
- "preset": "ts-jest",
21
- "testEnvironment": "node"
22
- },
20
+ "files": [
21
+ "transpiled",
22
+ "readme.md",
23
+ "!**/tsconfig.tsbuildinfo",
24
+ "!**/*.test.*",
25
+ "!**/.env*"
26
+ ],
23
27
  "scripts": {
24
- "test": "jest",
28
+ "test": "echo \"Error: run tests from root\" && exit 1",
25
29
  "transpile": "npx tsc"
26
30
  },
31
+ "devDependencies": {
32
+ "@aws-sdk/client-s3": "3.1156.0",
33
+ "@aws-sdk/lib-storage": "3.1156.0",
34
+ "@aws-sdk/s3-request-presigner": "3.1156.0",
35
+ "cloudinary": "2.11.0",
36
+ "dotenv": "18.0.9"
37
+ },
27
38
  "peerDependencies": {
28
- "dotenv": "*"
39
+ "@aws-sdk/client-s3": "3.1156.0",
40
+ "@aws-sdk/lib-storage": "3.1156.0",
41
+ "@aws-sdk/s3-request-presigner": "3.1156.0",
42
+ "cloudinary": "2.11.0"
43
+ },
44
+ "peerDependenciesMeta": {
45
+ "@aws-sdk/client-s3": {
46
+ "optional": true
47
+ },
48
+ "@aws-sdk/lib-storage": {
49
+ "optional": true
50
+ },
51
+ "@aws-sdk/s3-request-presigner": {
52
+ "optional": true
53
+ },
54
+ "cloudinary": {
55
+ "optional": true
56
+ }
29
57
  },
30
58
  "dependencies": {
31
- "@aws-sdk/client-s3": "3.481.0",
32
- "@aws-sdk/lib-storage": "3.481.0",
33
- "@toa.io/agent": "1.0.0-alpha.34",
34
- "@toa.io/generic": "1.0.0-alpha.34",
35
- "@toa.io/schemas": "1.0.0-alpha.34",
36
- "error-value": "0.3.0",
37
- "matchacho": "0.6.0",
38
- "msgpackr": "1.10.1",
39
- "smithy-node-native-fetch": "1.0.6"
40
- },
41
- "gitHead": "caf71c04ebd589ff54a25f533b975484aaf9a254"
59
+ "@toa.io/core": "1.0.0-alpha.333",
60
+ "@toa.io/definitions": "1.0.0-alpha.340",
61
+ "@toa.io/generic": "1.0.0-alpha.332",
62
+ "@toa.io/schemas": "1.0.0-alpha.332",
63
+ "negotiator": "1.1.0",
64
+ "openspan": "1.0.0-alpha.330"
65
+ },
66
+ "gitHead": "1871346ef431050d23bd8e91f2b920cea1aeb749"
42
67
  }
package/readme.md CHANGED
@@ -2,19 +2,31 @@
2
2
 
3
3
  Shared BLOB storage.
4
4
 
5
+ ## Installing a provider
6
+
7
+ A provider's SDK is not installed with this extension: `s3` and `spaces` need
8
+ `@aws-sdk/client-s3`, `@aws-sdk/lib-storage` and `@aws-sdk/s3-request-presigner`, `cloudinary`
9
+ needs `cloudinary`, and `fs`, `tmp` and `test` need nothing. A deploy installs what the components
10
+ of each composition declare, so an image carries the SDK of a provider it uses and no other, and a
11
+ process makes the storages its components declare — every one, for a component that names none.
12
+
13
+ A workspace installs the same set itself, once, and again when a storage is added:
14
+
15
+ ```shell
16
+ $ toa npm -p application
17
+ ```
18
+
19
+ Without it a composition fails to start, naming the package and version it wants.
20
+
5
21
  ## Entry
6
22
 
7
23
  BLOBs are stored with the meta-information object (Entry) having the following properties:
8
24
 
9
- - `id` - checksum
10
- - `size` - size in bytes
11
- - `type` - MIME type
12
- - `created` - creation timestamp (UNIX time, ms)
13
- - `variants` - array of:
14
- - `name` - unique name
15
- - `size` - size in bytes
16
- - `type` - variant MIME type
17
- - `meta` - object with application-specific information, empty by default
25
+ - `size`: size in bytes
26
+ - `type`: MIME type
27
+ - `checksum`: content checksum
28
+ - `created`: creation timestamp (ISO 8601)
29
+ - `attributes`: `Record<string, string>` with application-specific information, empty by default
18
30
 
19
31
  ### Example
20
32
 
@@ -22,12 +34,7 @@ BLOBs are stored with the meta-information object (Entry) having the following p
22
34
  id: eecd837c
23
35
  type: image/jpeg
24
36
  created: 1698004822358
25
- variants:
26
- - name: thumbnail.jpeg
27
- type: image/jpeg
28
- - name: thumbnail.webp
29
- type: image/webp
30
- meta:
37
+ attributes:
31
38
  face: true
32
39
  nudity: false
33
40
  ```
@@ -38,8 +45,8 @@ The Storages extension provides `storages` aspect,
38
45
  containing named Storage instances, according to the annotation.
39
46
 
40
47
  ```javascript
41
- async function effect (_, context) {
42
- await context.storages.photos.fetch('/path/to/b4f577e0.thumbnail.jpeg')
48
+ async function effect(_, context) {
49
+ await context.storages.photos.get('/path/to/b4f577e0.thumbnail.jpeg')
43
50
  }
44
51
  ```
45
52
 
@@ -47,13 +54,15 @@ async function effect (_, context) {
47
54
 
48
55
  > `Maybe<T> = T | Error`
49
56
 
57
+ An error has a `code`, and an operation that returns it refuses with that code.
58
+
50
59
  #### `async put(path: string, stream: Readable, options?: Options): Maybe<Entry>`
51
60
 
52
- ```
61
+ ```ts
53
62
  interface Options {
54
63
  claim?: string
55
64
  accept?: string
56
- meta?: Record<string, string>
65
+ attributes?: Record<string, string>
57
66
  }
58
67
  ```
59
68
 
@@ -75,13 +84,13 @@ are: `image/jpeg`, `image/png`, `image/gif`, `image/webp`, `image/heic`, `image/
75
84
 
76
85
  See [source](source/Scanner.ts).
77
86
 
78
- #### `async get(path: string): Maybe<Entry>`
87
+ #### `async head(path: string): Maybe<Entry>`
79
88
 
80
89
  Get an entry.
81
90
 
82
91
  If the entry does not exist, a `NOT_FOUND` error is returned.
83
92
 
84
- #### `async fetch(path: string): Maybe<Readable>`
93
+ #### `async get(path: string): Maybe<Readable>`
85
94
 
86
95
  Fetch the BLOB specified by `path`. If the path does not exist, a `NOT_FOUND` error is returned.
87
96
 
@@ -91,6 +100,15 @@ Fetch the BLOB specified by `path`. If the path does not exist, a `NOT_FOUND` er
91
100
  - `/path/to/eecd837c` - fetch the BLOB by path
92
101
  - `/path/to/eecd837c.thumbnail.jpeg` - fetch the `thumbnail.jpeg` variant of the BLOB
93
102
 
103
+ #### `async url(path: string, options?: { expires?: number }): Maybe<string>`
104
+
105
+ Where the file is read straight from the provider, by whoever is given the address and with no
106
+ credentials of their own. `s3` and `spaces` sign it for `expires` seconds — an hour by default, a
107
+ week at most — and serve the file as the type it was stored as; `cloudinary` gives where it
108
+ delivers the file, or a variant of it by extension, unsigned and without asking whether the file
109
+ is there. `NOT_FOUND` is a file an `s3` or `spaces` storage does not have; `NO_URL` is a storage
110
+ that serves no address of its own: `fs`, `tmp` and `test`.
111
+
94
112
  #### `async delete(path: string): Maybe<void>`
95
113
 
96
114
  Delete the entry specified by `path`.
@@ -110,18 +128,6 @@ await storage.move('/path/to/eecd837c', './sub/eecd837c')
110
128
  await storage.move('/path/to/eecd837c', './sub/')
111
129
  ```
112
130
 
113
- #### `async entries(path: string): Entry[]`
114
-
115
- Get a list of entries under the `path`.
116
-
117
- #### `async diversify(path: string, name: string, stream: Readable): Maybe<void>`
118
-
119
- Add or replace a `name` variant of the entry specified by `path`.
120
-
121
- #### `async annotate(path: string, key: string, value: any): Maybe<void>`
122
-
123
- Set a `key` property in the `meta` of the entry specified by `path`.
124
-
125
131
  ## Providers
126
132
 
127
133
  Storage uses underlying providers to store BLOBs and entries.
@@ -152,40 +158,114 @@ and [`toa env`](/runtime/cli/readme.md#env)
152
158
  for local environment.
153
159
  `endpoint` parameter is optional.
154
160
 
155
- ### Filesystem
161
+ ### DigitalOcean Spaces
156
162
 
157
163
  Annotation format is:
158
164
 
159
165
  ```yaml
160
166
  storages:
161
- photos@dev:
162
- provider: fs
163
- path: /var/my-storage
167
+ photos:
168
+ provider: spaces
169
+ space: my-space
170
+ region: fra1
164
171
  ```
165
172
 
166
- ### Temporary
173
+ `region` is the datacenter slug, the endpoint is derived from it.
167
174
 
168
- Filesystem using OS temporary directory.
175
+ Secrets `ACCESS_KEY_ID` and `SECRET_ACCESS_KEY` are required.
176
+ Keys are created in the DigitalOcean control panel,
177
+ under Spaces Object Storage, on the Access Keys tab.
178
+
179
+ ### Cloudinary
180
+
181
+ [Cloudinary](https://cloudinary.com) provider is used to store and transform media files.
182
+
183
+ Objects stored in the Cloudinary storage can be requested with transformations,
184
+ using dot-separated path extensions: `/path/to/eecd837c.{extension}.{extension}`.
185
+
186
+ Transformation is defined by the `extension` property, which is a regular expression with named
187
+ groups.
188
+ The named groups are used to replace the placeholders in the `transform` object.
189
+
190
+ `transform` object is an element of
191
+ the [Cloudinary `transformation` array](https://cloudinary.com/documentation/node_image_manipulation#apply_common_image_transformations).
192
+
193
+ > - `zoom` should be specified as integer [0–100].
194
+ > - `format` value `jpeg` is converted to `jpg`.
195
+ > - `^` and `$` are added to the regular expression automatically, unless they are already present.
169
196
 
170
197
  Annotation format is:
171
198
 
172
199
  ```yaml
173
200
  storages:
174
- photos@dev:
175
- provider: tmp
176
- directory: my-app-tmp
201
+ media:
202
+ provider: cloudinary
203
+ environment: my-cloud
204
+ type: image # image or video
205
+ prefix: my-app
206
+ transformations:
207
+ - extension: (?<width>\d*)x(?<height>\d*)(z(?<zoom>\d*))?
208
+ transformation:
209
+ width: <width>
210
+ height: <height>
211
+ zoom: <zoom>
212
+ crop: thumb
213
+ gravity: face
214
+ optional: true
215
+ - extension: (?<format>jpeg|webp)
216
+ transformation:
217
+ fetch_format: <format>
218
+ ```
219
+
220
+ Path extensions must be specified in the same order as in the annotation.
221
+
222
+ For the example above:
223
+
224
+ - `/path/to/eecd837c.100x100.jpeg`: matches
225
+ - `/path/to/eecd837c.jpeg.100x100`: does not match
226
+
227
+ If a path extension is not matched,
228
+ or if at least one of the transformations is not matched (unless it is `optional`),
229
+ then an error is returned.
230
+
231
+ Secrets:
232
+
233
+ - `API_KEY`
234
+ - `API_SECRET`
235
+
236
+ ### Filesystem
237
+
238
+ Annotation format is:
239
+
240
+ ```yaml
241
+ storages:
242
+ photos:
243
+ provider: fs
244
+ path: /var/my-photos
177
245
  ```
178
246
 
179
- ### Memory
247
+ [Kubernetes PVC](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) can be mounted to
248
+ the storage:
180
249
 
181
- In-memory non-persistent storage.
250
+ ```yaml
251
+ storages:
252
+ photos:
253
+ provider: fs
254
+ path: /var/my-photos
255
+ claim: photos-pvc
256
+ ```
182
257
 
183
- Annotation value format is:
258
+ ### Temporary
259
+
260
+ Filesystem using OS temporary directory.
261
+
262
+ Annotation format is:
184
263
 
185
264
  ```yaml
186
265
  storages:
187
266
  photos@dev:
188
- provider: mem
267
+ provider: tmp
268
+ directory: my-app-tmp
189
269
  ```
190
270
 
191
271
  ## Deduplication
@@ -196,7 +276,7 @@ Variants, on the other hand, are not deduplicated across different entries.
196
276
 
197
277
  Underlying directory structure:
198
278
 
199
- ```
279
+ ```text
200
280
  /temp
201
281
  c28f4dfd # random id
202
282
  /blobs
@@ -1,8 +1,12 @@
1
- import { Connector, type extensions } from '@toa.io/core';
2
- import { type Storage, type Storages } from './Storage';
1
+ import { Connector } from '@toa.io/core';
2
+ import type { extensions } from '@toa.io/core/types';
3
+ import { type Storage, type Storages } from './Storage.ts';
3
4
  export declare class Aspect extends Connector implements extensions.Aspect {
4
5
  readonly name = "storages";
5
- private readonly storages;
6
- constructor(storages: Storages);
6
+ private readonly create;
7
+ private storages;
8
+ /** The storages are made as the aspect connects: their providers load then, not before. */
9
+ constructor(create: () => Promise<Storages>);
10
+ protected open(): Promise<void>;
7
11
  invoke(name: string, method: keyof Storage, ...args: unknown[]): unknown;
8
12
  }
@@ -1,25 +1,23 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.Aspect = void 0;
7
- const node_assert_1 = __importDefault(require("node:assert"));
8
- const core_1 = require("@toa.io/core");
9
- class Aspect extends core_1.Connector {
1
+ import { Connector } from '@toa.io/core';
2
+ export class Aspect extends Connector {
10
3
  name = 'storages';
11
- storages;
12
- constructor(storages) {
4
+ create;
5
+ storages = {};
6
+ /** The storages are made as the aspect connects: their providers load then, not before. */
7
+ constructor(create) {
13
8
  super();
14
- this.storages = storages;
9
+ this.create = create;
10
+ }
11
+ async open() {
12
+ this.storages = await this.create();
15
13
  }
16
14
  invoke(name, method, ...args) {
17
15
  const storage = this.storages[name];
18
- node_assert_1.default.ok(storage !== undefined, `Storage '${name}' is not defined`);
16
+ if (storage === undefined)
17
+ throw new Error(`Storage '${name}' is not defined`);
19
18
  // eslint-disable-next-line @typescript-eslint/ban-ts-comment
20
19
  // @ts-expect-error
21
20
  return storage[method](...args);
22
21
  }
23
22
  }
24
- exports.Aspect = Aspect;
25
23
  //# sourceMappingURL=Aspect.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Aspect.js","sourceRoot":"","sources":["../source/Aspect.ts"],"names":[],"mappings":";;;;;;AAAA,8DAAgC;AAChC,uCAAyD;AAGzD,MAAa,MAAO,SAAQ,gBAAS;IACnB,IAAI,GAAG,UAAU,CAAA;IAEhB,QAAQ,CAAU;IAEnC,YAAoB,QAAkB;QACpC,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC1B,CAAC;IAEM,MAAM,CAAE,IAAY,EAAE,MAAqB,EAAE,GAAG,IAAe;QACpE,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAEnC,qBAAM,CAAC,EAAE,CAAC,OAAO,KAAK,SAAS,EAAE,YAAY,IAAI,kBAAkB,CAAC,CAAA;QAEpE,6DAA6D;QAC7D,mBAAmB;QACnB,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;IACjC,CAAC;CACF;AApBD,wBAoBC"}
1
+ {"version":3,"file":"Aspect.js","sourceRoot":"","sources":["../source/Aspect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAIxC,MAAM,OAAO,MAAO,SAAQ,SAAS;IACnB,IAAI,GAAG,UAAU,CAAA;IAEhB,MAAM,CAAyB;IACxC,QAAQ,GAAa,EAAE,CAAA;IAE/B,2FAA2F;IAC3F,YAAmB,MAA+B;QAChD,KAAK,EAAE,CAAA;QAEP,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACtB,CAAC;IAEkB,KAAK,CAAC,IAAI;QAC3B,IAAI,CAAC,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,EAAE,CAAA;IACrC,CAAC;IAEM,MAAM,CAAC,IAAY,EAAE,MAAqB,EAAE,GAAG,IAAe;QACnE,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAA;QAEnC,IAAI,OAAO,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,YAAY,IAAI,kBAAkB,CAAC,CAAA;QAE9E,6DAA6D;QAC7D,mBAAmB;QACnB,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;IACjC,CAAC;CACF"}
@@ -1,14 +1,17 @@
1
- export interface Entry {
1
+ import type { Readable } from 'node:stream';
2
+ export type Entry = {
2
3
  id: string;
3
- size: number;
4
+ } & Metadata;
5
+ export type Stream = {
6
+ stream: Readable;
7
+ } & Metadata;
8
+ export interface Metadata {
4
9
  type: string;
5
- created: number;
6
- variants: Variant[];
7
- meta: Record<string, unknown>;
10
+ size: number | null;
11
+ checksum: string;
12
+ created: string;
13
+ attributes: Attributes;
14
+ range?: string;
15
+ partial?: boolean;
8
16
  }
9
- interface Variant {
10
- name: string;
11
- size: number;
12
- type: string;
13
- }
14
- export {};
17
+ export type Attributes = Record<string, string>;
@@ -1,3 +1,2 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ export {};
3
2
  //# sourceMappingURL=Entry.js.map
@@ -1,8 +1,16 @@
1
- import { Aspect } from './Aspect';
1
+ import { Aspect } from './Aspect.ts';
2
2
  export declare class Factory {
3
3
  private readonly annotation;
4
+ /** The storages made so far, by name: one for the process, whichever component declares it. */
5
+ private readonly made;
4
6
  constructor();
5
- aspect(): Aspect;
7
+ /**
8
+ * The storages of a component: those it declares, and every storage there is where it names
9
+ * none (`storages: ~`). A deploy installs the SDKs of the providers a composition's components
10
+ * declare and no other, so a storage nobody in the process declares is not made: its provider
11
+ * may not be there.
12
+ */
13
+ aspect(_: unknown, declaration?: string[] | null): Aspect;
6
14
  private createStorages;
7
15
  private createStorage;
8
16
  private resolveSecrets;
@@ -1,53 +1,73 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.Factory = void 0;
7
- const node_assert_1 = __importDefault(require("node:assert"));
8
- const generic_1 = require("@toa.io/generic");
9
- const providers_1 = require("./providers");
10
- const Storage_1 = require("./Storage");
11
- const Aspect_1 = require("./Aspect");
12
- const deployment_1 = require("./deployment");
13
- const Annotation_1 = require("./Annotation");
14
- class Factory {
1
+ import { console } from 'openspan';
2
+ import { provider as load } from './providers/index.js';
3
+ import { Storage } from './Storage.js';
4
+ import { Aspect } from './Aspect.js';
5
+ import { ENV_PREFIX, validateAnnotation } from '@toa.io/definitions/extensions.storages';
6
+ import { environment } from '@toa.io/generic';
7
+ export class Factory {
15
8
  annotation;
9
+ /** The storages made so far, by name: one for the process, whichever component declares it. */
10
+ made = new Map();
16
11
  constructor() {
17
- const env = process.env.TOA_STORAGES;
18
- node_assert_1.default.ok(env !== undefined, 'TOA_STORAGES is not defined');
19
- this.annotation = (0, generic_1.decode)(env);
20
- (0, Annotation_1.validateAnnotation)(this.annotation);
12
+ const env = environment.get(ENV_PREFIX);
13
+ if (env === undefined)
14
+ throw new Error(`${ENV_PREFIX} is not defined`);
15
+ this.annotation = JSON.parse(env);
16
+ validateAnnotation(this.annotation);
21
17
  }
22
- aspect() {
23
- const storages = this.createStorages();
24
- return new Aspect_1.Aspect(storages);
18
+ /**
19
+ * The storages of a component: those it declares, and every storage there is where it names
20
+ * none (`storages: ~`). A deploy installs the SDKs of the providers a composition's components
21
+ * declare and no other, so a storage nobody in the process declares is not made: its provider
22
+ * may not be there.
23
+ */
24
+ aspect(_, declaration) {
25
+ const names = declaration === undefined || declaration === null || declaration.length === 0
26
+ ? Object.keys(this.annotation)
27
+ : declaration;
28
+ return new Aspect(() => this.createStorages(names));
25
29
  }
26
- createStorages() {
30
+ async createStorages(names) {
27
31
  const storages = {};
28
- for (const [name, declaration] of Object.entries(this.annotation))
29
- storages[name] = this.createStorage(name, declaration);
32
+ for (const name of names) {
33
+ const declaration = this.annotation[name];
34
+ // one the annotation does not declare is refused as it is used (`Aspect.invoke`)
35
+ if (declaration === undefined)
36
+ continue;
37
+ let storage = this.made.get(name);
38
+ if (storage === undefined) {
39
+ // the annotation is validated above, so a declaration is its provider's
40
+ storage = this.createStorage(name, declaration);
41
+ this.made.set(name, storage);
42
+ }
43
+ storages[name] = await storage;
44
+ }
30
45
  return storages;
31
46
  }
32
- createStorage(name, declaration) {
33
- const { provider: providerId, ...options } = declaration;
34
- const Provider = providers_1.providers[providerId];
47
+ async createStorage(name, declaration) {
48
+ const { provider: id, ...options } = declaration;
49
+ const Provider = await load(id);
35
50
  const secrets = this.resolveSecrets(name, Provider);
36
51
  const provider = new Provider(options, secrets);
37
- return new Storage_1.Storage(provider);
52
+ console.debug('Storage created', {
53
+ name,
54
+ provider: id,
55
+ ...(provider.root === undefined ? undefined : { root: provider.root })
56
+ });
57
+ return new Storage(provider, { name, provider: id });
38
58
  }
39
59
  resolveSecrets(storageName, Class) {
40
60
  if (Class.SECRETS === undefined)
41
61
  return {};
42
62
  const secrets = {};
43
63
  for (const secret of Class.SECRETS) {
44
- const variable = `${deployment_1.SERIALIZATION_PREFIX}_${storageName}_${secret.name}`.toUpperCase();
45
- const value = process.env[variable];
46
- node_assert_1.default.ok(secret.optional === true || value !== undefined, `'${variable}' is not defined`);
64
+ const variable = `${ENV_PREFIX}_${storageName}_${secret.name}`.toUpperCase();
65
+ const value = environment.get(variable);
66
+ if (secret.optional !== true && value === undefined)
67
+ throw new Error(`'${variable}' is not defined`);
47
68
  secrets[secret.name] = value;
48
69
  }
49
70
  return secrets;
50
71
  }
51
72
  }
52
- exports.Factory = Factory;
53
73
  //# sourceMappingURL=Factory.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":";;;;;;AAAA,8DAAgC;AAChC,6CAAwC;AACxC,2CAAuC;AACvC,uCAAkD;AAClD,qCAAiC;AACjC,6CAAmD;AACnD,6CAAiD;AAKjD,MAAa,OAAO;IACD,UAAU,CAAY;IAEvC;QACE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAA;QAEpC,qBAAM,CAAC,EAAE,CAAC,GAAG,KAAK,SAAS,EAAE,6BAA6B,CAAC,CAAA;QAE3D,IAAI,CAAC,UAAU,GAAG,IAAA,gBAAM,EAAC,GAAG,CAAC,CAAA;QAE7B,IAAA,+BAAkB,EAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACrC,CAAC;IAEM,MAAM;QACX,MAAM,QAAQ,GAAG,IAAI,CAAC,cAAc,EAAE,CAAA;QAEtC,OAAO,IAAI,eAAM,CAAC,QAAQ,CAAC,CAAA;IAC7B,CAAC;IAEO,cAAc;QACpB,MAAM,QAAQ,GAAa,EAAE,CAAA;QAE7B,KAAK,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC;YAC/D,QAAQ,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,WAAW,CAAC,CAAA;QAExD,OAAO,QAAQ,CAAA;IACjB,CAAC;IAEO,aAAa,CAAE,IAAY,EAAE,WAAwB;QAC3D,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,OAAO,EAAE,GAAG,WAAW,CAAA;QACxD,MAAM,QAAQ,GAAwB,qBAAS,CAAC,UAAU,CAAC,CAAA;QAC3D,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QACnD,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAE/C,OAAO,IAAI,iBAAO,CAAC,QAAQ,CAAC,CAAA;IAC9B,CAAC;IAEO,cAAc,CAAE,WAAmB,EACzC,KAA0B;QAC1B,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;YAC7B,OAAO,EAAE,CAAA;QAEX,MAAM,OAAO,GAAuC,EAAE,CAAA;QAEtD,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACnC,MAAM,QAAQ,GAAG,GAAG,iCAAoB,IAAI,WAAW,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;YACtF,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;YAEnC,qBAAM,CAAC,EAAE,CAAC,MAAM,CAAC,QAAQ,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,EACvD,IAAI,QAAQ,kBAAkB,CAAC,CAAA;YAEjC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAA;QAC9B,CAAC;QAED,OAAO,OAAO,CAAA;IAChB,CAAC;CACF;AAxDD,0BAwDC"}
1
+ {"version":3,"file":"Factory.js","sourceRoot":"","sources":["../source/Factory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAClC,OAAO,EAAE,QAAQ,IAAI,IAAI,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,EAAE,OAAO,EAAiB,MAAM,cAAc,CAAA;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,yCAAyC,CAAA;AACxF,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAM7C,MAAM,OAAO,OAAO;IACD,UAAU,CAAY;IAEvC,+FAA+F;IAC9E,IAAI,GAAG,IAAI,GAAG,EAA4B,CAAA;IAE3D;QACE,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;QAEvC,IAAI,GAAG,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,UAAU,iBAAiB,CAAC,CAAA;QAEtE,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QAEjC,kBAAkB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;IACrC,CAAC;IAED;;;;;OAKG;IACI,MAAM,CAAC,CAAU,EAAE,WAA6B;QACrD,MAAM,KAAK,GACT,WAAW,KAAK,SAAS,IAAI,WAAW,KAAK,IAAI,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC;YAC3E,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC;YAC9B,CAAC,CAAC,WAAW,CAAA;QAEjB,OAAO,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAA;IACrD,CAAC;IAEO,KAAK,CAAC,cAAc,CAAC,KAAe;QAC1C,MAAM,QAAQ,GAAa,EAAE,CAAA;QAE7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAA;YAEzC,iFAAiF;YACjF,IAAI,WAAW,KAAK,SAAS;gBAAE,SAAQ;YAEvC,IAAI,OAAO,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;YAEjC,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC1B,wEAAwE;gBACxE,OAAO,GAAG,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,WAA0B,CAAC,CAAA;gBAC9D,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;YAC9B,CAAC;YAED,QAAQ,CAAC,IAAI,CAAC,GAAG,MAAM,OAAO,CAAA;QAChC,CAAC;QAED,OAAO,QAAQ,CAAA;IACjB,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,IAAY,EAAE,WAAwB;QAChE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,GAAG,OAAO,EAAE,GAAG,WAAW,CAAA;QAChD,MAAM,QAAQ,GAAgB,MAAM,IAAI,CAAC,EAAE,CAAC,CAAA;QAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,cAAc,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QACnD,MAAM,QAAQ,GAAG,IAAI,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;QAE/C,OAAO,CAAC,KAAK,CAAC,iBAAiB,EAAE;YAC/B,IAAI;YACJ,QAAQ,EAAE,EAAE;YACZ,GAAG,CAAC,QAAQ,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,CAAC,IAAI,EAAE,CAAC;SACvE,CAAC,CAAA;QAEF,OAAO,IAAI,OAAO,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC,CAAA;IACtD,CAAC;IAEO,cAAc,CAAC,WAAmB,EAAE,KAAkB;QAC5D,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;YAAE,OAAO,EAAE,CAAA;QAE1C,MAAM,OAAO,GAAuC,EAAE,CAAA;QAEtD,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACnC,MAAM,QAAQ,GAAG,GAAG,UAAU,IAAI,WAAW,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;YAC5E,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;YAEvC,IAAI,MAAM,CAAC,QAAQ,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS;gBACjD,MAAM,IAAI,KAAK,CAAC,IAAI,QAAQ,kBAAkB,CAAC,CAAA;YAEjD,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAA;QAC9B,CAAC;QAED,OAAO,OAAO,CAAA;IAChB,CAAC;CACF"}
@@ -1,22 +1,26 @@
1
- /// <reference types="node" />
2
- import { type Readable } from 'node:stream';
3
- export type ProviderSecrets<K extends string = string> = Record<K | string, string | undefined>;
4
- export interface ProviderSecret {
5
- readonly name: string;
6
- readonly optional?: boolean;
7
- }
8
- export declare abstract class Provider<Options = void> {
9
- static readonly SECRETS: readonly ProviderSecret[];
10
- constructor(_: Options, secrets?: ProviderSecrets);
11
- abstract get(path: string): Promise<Readable | null>;
12
- abstract list(path: string): Promise<string[]>;
13
- abstract put(path: string, filename: string, stream: Readable): Promise<void>;
1
+ import type { Metadata, Stream } from './Entry.ts';
2
+ import type { Readable } from 'node:stream';
3
+ import type { Maybe } from '@toa.io/core/types';
4
+ import type { Secret, Secrets } from './Secrets.ts';
5
+ export declare abstract class Provider<Options = unknown> {
6
+ static readonly SECRETS?: readonly Secret[];
7
+ readonly root?: string;
8
+ readonly options: Options;
9
+ protected constructor(options: Options, secrets?: Secrets);
10
+ abstract get(path: string, options?: unknown): Promise<Maybe<Stream>>;
11
+ abstract head(path: string): Promise<Maybe<Metadata>>;
12
+ abstract put(path: string, stream: Readable): Promise<void>;
13
+ abstract commit(path: string, metadata: Metadata): Promise<void>;
14
14
  abstract delete(path: string): Promise<void>;
15
- abstract move(from: string, to: string): Promise<void>;
16
- abstract moveDir(from: string, to: string): Promise<void>;
15
+ abstract move(from: string, to: string): Promise<Maybe<void>>;
16
+ /**
17
+ * Where whoever is given it reads the file straight from the provider, with no credentials of
18
+ * their own, for `expires` seconds where the address is signed. A provider that serves no
19
+ * address of its own answers `NO_URL`.
20
+ */
21
+ url(_path: string, _expires: number): Promise<Maybe<string>>;
17
22
  }
18
- export interface ProviderConstructor {
19
- readonly SECRETS: readonly ProviderSecret[];
20
- prototype: Provider;
21
- new (options: any, secrets?: ProviderSecrets): Provider;
23
+ export interface Constructor<Options = any> {
24
+ SECRETS?: readonly Secret[];
25
+ new (options: Options, secrets?: Secrets): Provider<Options>;
22
26
  }