@deenruv/asset-server-plugin 1.0.0

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 (37) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +103 -0
  3. package/lib/index.d.ts +4 -0
  4. package/lib/index.js +21 -0
  5. package/lib/index.js.map +1 -0
  6. package/lib/src/common.d.ts +4 -0
  7. package/lib/src/common.js +42 -0
  8. package/lib/src/common.js.map +1 -0
  9. package/lib/src/constants.d.ts +2 -0
  10. package/lib/src/constants.js +6 -0
  11. package/lib/src/constants.js.map +1 -0
  12. package/lib/src/default-asset-storage-strategy-factory.d.ts +6 -0
  13. package/lib/src/default-asset-storage-strategy-factory.js +24 -0
  14. package/lib/src/default-asset-storage-strategy-factory.js.map +1 -0
  15. package/lib/src/file-icon.png +0 -0
  16. package/lib/src/hashed-asset-naming-strategy.d.ts +21 -0
  17. package/lib/src/hashed-asset-naming-strategy.js +39 -0
  18. package/lib/src/hashed-asset-naming-strategy.js.map +1 -0
  19. package/lib/src/local-asset-storage-strategy.d.ts +28 -0
  20. package/lib/src/local-asset-storage-strategy.js +68 -0
  21. package/lib/src/local-asset-storage-strategy.js.map +1 -0
  22. package/lib/src/plugin.d.ts +167 -0
  23. package/lib/src/plugin.js +394 -0
  24. package/lib/src/plugin.js.map +1 -0
  25. package/lib/src/s3-asset-storage-strategy.d.ts +159 -0
  26. package/lib/src/s3-asset-storage-strategy.js +289 -0
  27. package/lib/src/s3-asset-storage-strategy.js.map +1 -0
  28. package/lib/src/sharp-asset-preview-strategy.d.ts +99 -0
  29. package/lib/src/sharp-asset-preview-strategy.js +121 -0
  30. package/lib/src/sharp-asset-preview-strategy.js.map +1 -0
  31. package/lib/src/transform-image.d.ts +25 -0
  32. package/lib/src/transform-image.js +145 -0
  33. package/lib/src/transform-image.js.map +1 -0
  34. package/lib/src/types.d.ts +137 -0
  35. package/lib/src/types.js +3 -0
  36. package/lib/src/types.js.map +1 -0
  37. package/package.json +47 -0
@@ -0,0 +1,394 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __importDefault = (this && this.__importDefault) || function (mod) {
12
+ return (mod && mod.__esModule) ? mod : { "default": mod };
13
+ };
14
+ var AssetServerPlugin_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.AssetServerPlugin = void 0;
17
+ const core_1 = require("@deenruv/core");
18
+ const crypto_1 = require("crypto");
19
+ const express_1 = __importDefault(require("express"));
20
+ const fs_extra_1 = __importDefault(require("fs-extra"));
21
+ const path_1 = __importDefault(require("path"));
22
+ const common_1 = require("./common");
23
+ const constants_1 = require("./constants");
24
+ const default_asset_storage_strategy_factory_1 = require("./default-asset-storage-strategy-factory");
25
+ const hashed_asset_naming_strategy_1 = require("./hashed-asset-naming-strategy");
26
+ const sharp_asset_preview_strategy_1 = require("./sharp-asset-preview-strategy");
27
+ const transform_image_1 = require("./transform-image");
28
+ async function getFileType(buffer) {
29
+ const { fileTypeFromBuffer } = await import("file-type");
30
+ return fileTypeFromBuffer(buffer);
31
+ }
32
+ /**
33
+ * @description
34
+ * The `AssetServerPlugin` serves assets (images and other files) from the local file system, and can also be configured to use
35
+ * other storage strategies (e.g. {@link S3AssetStorageStrategy}. It can also perform on-the-fly image transformations
36
+ * and caches the results for subsequent calls.
37
+ *
38
+ * ## Installation
39
+ *
40
+ * `yarn add \@deenruv/asset-server-plugin`
41
+ *
42
+ * or
43
+ *
44
+ * `npm install \@deenruv/asset-server-plugin`
45
+ *
46
+ * @example
47
+ * ```ts
48
+ * import { AssetServerPlugin } from '\@deenruv/asset-server-plugin';
49
+ *
50
+ * const config: DeenruvConfig = {
51
+ * // Add an instance of the plugin to the plugins array
52
+ * plugins: [
53
+ * AssetServerPlugin.init({
54
+ * route: 'assets',
55
+ * assetUploadDir: path.join(__dirname, 'assets'),
56
+ * }),
57
+ * ],
58
+ * };
59
+ * ```
60
+ *
61
+ * The full configuration is documented at [AssetServerOptions](/reference/core-plugins/asset-server-plugin/asset-server-options)
62
+ *
63
+ * ## Image transformation
64
+ *
65
+ * Asset preview images can be transformed (resized & cropped) on the fly by appending query parameters to the url:
66
+ *
67
+ * `http://localhost:3000/assets/some-asset.jpg?w=500&h=300&mode=resize`
68
+ *
69
+ * The above URL will return `some-asset.jpg`, resized to fit in the bounds of a 500px x 300px rectangle.
70
+ *
71
+ * ### Preview mode
72
+ *
73
+ * The `mode` parameter can be either `crop` or `resize`. See the [ImageTransformMode](/reference/core-plugins/asset-server-plugin/image-transform-mode) docs for details.
74
+ *
75
+ * ### Focal point
76
+ *
77
+ * When cropping an image (`mode=crop`), Deenruv will attempt to keep the most "interesting" area of the image in the cropped frame. It does this
78
+ * by finding the area of the image with highest entropy (the busiest area of the image). However, sometimes this does not yield a satisfactory
79
+ * result - part or all of the main subject may still be cropped out.
80
+ *
81
+ * This is where specifying the focal point can help. The focal point of the image may be specified by passing the `fpx` and `fpy` query parameters.
82
+ * These are normalized coordinates (i.e. a number between 0 and 1), so the `fpx=0&fpy=0` corresponds to the top left of the image.
83
+ *
84
+ * For example, let's say there is a very wide landscape image which we want to crop to be square. The main subject is a house to the far left of the
85
+ * image. The following query would crop it to a square with the house centered:
86
+ *
87
+ * `http://localhost:3000/assets/landscape.jpg?w=150&h=150&mode=crop&fpx=0.2&fpy=0.7`
88
+ *
89
+ * ### Format
90
+ *
91
+ * Since v1.7.0, the image format can be specified by adding the `format` query parameter:
92
+ *
93
+ * `http://localhost:3000/assets/some-asset.jpg?format=webp`
94
+ *
95
+ * This means that, no matter the format of your original asset files, you can use more modern formats in your storefront if the browser
96
+ * supports them. Supported values for `format` are:
97
+ *
98
+ * * `jpeg` or `jpg`
99
+ * * `png`
100
+ * * `webp`
101
+ * * `avif`
102
+ *
103
+ * The `format` parameter can also be combined with presets (see below).
104
+ *
105
+ * ### Quality
106
+ *
107
+ * Since v2.2.0, the image quality can be specified by adding the `q` query parameter:
108
+ *
109
+ * `http://localhost:3000/assets/some-asset.jpg?q=75`
110
+ *
111
+ * This applies to the `jpg`, `webp` and `avif` formats. The default quality value for `jpg` and `webp` is 80, and for `avif` is 50.
112
+ *
113
+ * The `q` parameter can also be combined with presets (see below).
114
+ *
115
+ * ### Transform presets
116
+ *
117
+ * Presets can be defined which allow a single preset name to be used instead of specifying the width, height and mode. Presets are
118
+ * configured via the AssetServerOptions [presets property](/reference/core-plugins/asset-server-plugin/asset-server-options/#presets).
119
+ *
120
+ * For example, defining the following preset:
121
+ *
122
+ * ```ts
123
+ * AssetServerPlugin.init({
124
+ * // ...
125
+ * presets: [
126
+ * { name: 'my-preset', width: 85, height: 85, mode: 'crop' },
127
+ * ],
128
+ * }),
129
+ * ```
130
+ *
131
+ * means that a request to:
132
+ *
133
+ * `http://localhost:3000/assets/some-asset.jpg?preset=my-preset`
134
+ *
135
+ * is equivalent to:
136
+ *
137
+ * `http://localhost:3000/assets/some-asset.jpg?w=85&h=85&mode=crop`
138
+ *
139
+ * The AssetServerPlugin comes pre-configured with the following presets:
140
+ *
141
+ * name | width | height | mode
142
+ * -----|-------|--------|-----
143
+ * tiny | 50px | 50px | crop
144
+ * thumb | 150px | 150px | crop
145
+ * small | 300px | 300px | resize
146
+ * medium | 500px | 500px | resize
147
+ * large | 800px | 800px | resize
148
+ *
149
+ * ### Caching
150
+ * By default, the AssetServerPlugin will cache every transformed image, so that the transformation only needs to be performed a single time for
151
+ * a given configuration. Caching can be disabled per-request by setting the `?cache=false` query parameter.
152
+ *
153
+ * @docsCategory core plugins/AssetServerPlugin
154
+ */
155
+ let AssetServerPlugin = AssetServerPlugin_1 = class AssetServerPlugin {
156
+ /**
157
+ * @description
158
+ * Set the plugin options.
159
+ */
160
+ static init(options) {
161
+ AssetServerPlugin_1.options = options;
162
+ return this;
163
+ }
164
+ /** @internal */
165
+ static async configure(config) {
166
+ var _a;
167
+ const storageStrategyFactory = this.options.storageStrategyFactory || default_asset_storage_strategy_factory_1.defaultAssetStorageStrategyFactory;
168
+ this.assetStorage = await storageStrategyFactory(this.options);
169
+ config.assetOptions.assetPreviewStrategy =
170
+ (_a = this.options.previewStrategy) !== null && _a !== void 0 ? _a : new sharp_asset_preview_strategy_1.SharpAssetPreviewStrategy({
171
+ maxWidth: this.options.previewMaxWidth,
172
+ maxHeight: this.options.previewMaxHeight,
173
+ });
174
+ config.assetOptions.assetStorageStrategy = this.assetStorage;
175
+ config.assetOptions.assetNamingStrategy =
176
+ this.options.namingStrategy || new hashed_asset_naming_strategy_1.HashedAssetNamingStrategy();
177
+ return config;
178
+ }
179
+ constructor(processContext) {
180
+ this.processContext = processContext;
181
+ this.cacheDir = "cache";
182
+ this.presets = [
183
+ { name: "tiny", width: 50, height: 50, mode: "crop" },
184
+ { name: "thumb", width: 150, height: 150, mode: "crop" },
185
+ { name: "small", width: 300, height: 300, mode: "resize" },
186
+ { name: "medium", width: 500, height: 500, mode: "resize" },
187
+ { name: "large", width: 800, height: 800, mode: "resize" },
188
+ ];
189
+ }
190
+ /** @internal */
191
+ onApplicationBootstrap() {
192
+ if (this.processContext.isWorker) {
193
+ return;
194
+ }
195
+ if (AssetServerPlugin_1.options.presets) {
196
+ for (const preset of AssetServerPlugin_1.options.presets) {
197
+ const existingIndex = this.presets.findIndex((p) => p.name === preset.name);
198
+ if (-1 < existingIndex) {
199
+ this.presets.splice(existingIndex, 1, preset);
200
+ }
201
+ else {
202
+ this.presets.push(preset);
203
+ }
204
+ }
205
+ }
206
+ // Configure Cache-Control header
207
+ const { cacheHeader } = AssetServerPlugin_1.options;
208
+ if (!cacheHeader) {
209
+ this.cacheHeader = constants_1.DEFAULT_CACHE_HEADER;
210
+ }
211
+ else {
212
+ if (typeof cacheHeader === "string") {
213
+ this.cacheHeader = cacheHeader;
214
+ }
215
+ else {
216
+ this.cacheHeader = [
217
+ cacheHeader.restriction,
218
+ `max-age: ${cacheHeader.maxAge}`,
219
+ ]
220
+ .filter((value) => !!value)
221
+ .join(", ");
222
+ }
223
+ }
224
+ const cachePath = path_1.default.join(AssetServerPlugin_1.options.assetUploadDir, this.cacheDir);
225
+ fs_extra_1.default.ensureDirSync(cachePath);
226
+ }
227
+ configure(consumer) {
228
+ if (this.processContext.isWorker) {
229
+ return;
230
+ }
231
+ core_1.Logger.info("Creating asset server middleware", constants_1.loggerCtx);
232
+ consumer
233
+ .apply(this.createAssetServer())
234
+ .forRoutes(AssetServerPlugin_1.options.route);
235
+ (0, core_1.registerPluginStartupMessage)("Asset server", AssetServerPlugin_1.options.route);
236
+ }
237
+ /**
238
+ * Creates the image server instance
239
+ */
240
+ createAssetServer() {
241
+ const assetServer = express_1.default.Router();
242
+ assetServer.use(this.sendAsset(), this.generateTransformedImage());
243
+ return assetServer;
244
+ }
245
+ /**
246
+ * Reads the file requested and send the response to the browser.
247
+ */
248
+ sendAsset() {
249
+ return async (req, res, next) => {
250
+ var _a;
251
+ const key = this.getFileNameFromRequest(req);
252
+ try {
253
+ const file = await AssetServerPlugin_1.assetStorage.readFileToBuffer(key);
254
+ let mimeType = this.getMimeType(key);
255
+ if (!mimeType) {
256
+ mimeType =
257
+ ((_a = (await getFileType(file))) === null || _a === void 0 ? void 0 : _a.mime) || "application/octet-stream";
258
+ }
259
+ res.contentType(mimeType);
260
+ res.setHeader("content-security-policy", "default-src 'self'");
261
+ res.setHeader("Cache-Control", this.cacheHeader);
262
+ res.send(file);
263
+ }
264
+ catch (e) {
265
+ const err = new Error("File not found");
266
+ err.status = 404;
267
+ return next(err);
268
+ }
269
+ };
270
+ }
271
+ /**
272
+ * If an exception was thrown by the first handler, then it may be because a transformed image
273
+ * is being requested which does not yet exist. In this case, this handler will generate the
274
+ * transformed image, save it to cache, and serve the result as a response.
275
+ */
276
+ generateTransformedImage() {
277
+ return async (err, req, res, next) => {
278
+ var _a;
279
+ if (err && (err.status === 404 || err.statusCode === 404)) {
280
+ if (req.query) {
281
+ const decodedReqPath = decodeURIComponent(req.path);
282
+ core_1.Logger.debug(`Pre-cached Asset not found: ${decodedReqPath}`, constants_1.loggerCtx);
283
+ let file;
284
+ try {
285
+ file =
286
+ await AssetServerPlugin_1.assetStorage.readFileToBuffer(decodedReqPath);
287
+ }
288
+ catch (_err) {
289
+ res.status(404).send("Resource not found");
290
+ return;
291
+ }
292
+ const image = await (0, transform_image_1.transformImage)(file, req.query, this.presets || []);
293
+ try {
294
+ const imageBuffer = await image.toBuffer();
295
+ const cachedFileName = this.getFileNameFromRequest(req);
296
+ if (!req.query.cache || req.query.cache === "true") {
297
+ await AssetServerPlugin_1.assetStorage.writeFileFromBuffer(cachedFileName, imageBuffer);
298
+ core_1.Logger.debug(`Saved cached asset: ${cachedFileName}`, constants_1.loggerCtx);
299
+ }
300
+ let mimeType = this.getMimeType(cachedFileName);
301
+ if (!mimeType) {
302
+ mimeType = ((_a = (await getFileType(imageBuffer))) === null || _a === void 0 ? void 0 : _a.mime) || "image/jpeg";
303
+ }
304
+ res.set("Content-Type", mimeType);
305
+ res.setHeader("content-security-policy", "default-src 'self'");
306
+ res.send(imageBuffer);
307
+ return;
308
+ }
309
+ catch (e) {
310
+ core_1.Logger.error(e, constants_1.loggerCtx, e.stack);
311
+ res.status(500).send(e.message);
312
+ return;
313
+ }
314
+ }
315
+ }
316
+ next();
317
+ };
318
+ }
319
+ getFileNameFromRequest(req) {
320
+ const { w, h, mode, preset, fpx, fpy, format, q } = req.query;
321
+ const focalPoint = fpx && fpy ? `_fpx${fpx}_fpy${fpy}` : "";
322
+ const quality = q ? `_q${q}` : "";
323
+ const imageFormat = (0, common_1.getValidFormat)(format);
324
+ let imageParamsString = "";
325
+ if (w || h) {
326
+ const width = w || "";
327
+ const height = h || "";
328
+ imageParamsString = `_transform_w${width}_h${height}_m${mode}`;
329
+ }
330
+ else if (preset) {
331
+ if (this.presets && !!this.presets.find((p) => p.name === preset)) {
332
+ imageParamsString = `_transform_pre_${preset}`;
333
+ }
334
+ }
335
+ if (focalPoint) {
336
+ imageParamsString += focalPoint;
337
+ }
338
+ if (imageFormat) {
339
+ imageParamsString += imageFormat;
340
+ }
341
+ if (quality) {
342
+ imageParamsString += quality;
343
+ }
344
+ const decodedReqPath = decodeURIComponent(req.path);
345
+ if (imageParamsString !== "") {
346
+ const imageParamHash = this.md5(imageParamsString);
347
+ return path_1.default.join(this.cacheDir, this.addSuffix(decodedReqPath, imageParamHash, imageFormat));
348
+ }
349
+ else {
350
+ return decodedReqPath;
351
+ }
352
+ }
353
+ md5(input) {
354
+ return (0, crypto_1.createHash)("md5").update(input).digest("hex");
355
+ }
356
+ addSuffix(fileName, suffix, ext) {
357
+ const originalExt = path_1.default.extname(fileName);
358
+ const effectiveExt = ext ? `.${ext}` : originalExt;
359
+ const baseName = path_1.default.basename(fileName, originalExt);
360
+ const dirName = path_1.default.dirname(fileName);
361
+ return path_1.default.join(dirName, `${baseName}${suffix}${effectiveExt}`);
362
+ }
363
+ /**
364
+ * Attempt to get the mime type from the file name.
365
+ */
366
+ getMimeType(fileName) {
367
+ const ext = path_1.default.extname(fileName);
368
+ switch (ext) {
369
+ case ".jpg":
370
+ case ".jpeg":
371
+ return "image/jpeg";
372
+ case ".png":
373
+ return "image/png";
374
+ case ".gif":
375
+ return "image/gif";
376
+ case ".svg":
377
+ return "image/svg+xml";
378
+ case ".tiff":
379
+ return "image/tiff";
380
+ case ".webp":
381
+ return "image/webp";
382
+ }
383
+ }
384
+ };
385
+ exports.AssetServerPlugin = AssetServerPlugin;
386
+ exports.AssetServerPlugin = AssetServerPlugin = AssetServerPlugin_1 = __decorate([
387
+ (0, core_1.DeenruvPlugin)({
388
+ imports: [core_1.PluginCommonModule],
389
+ configuration: (config) => AssetServerPlugin.configure(config),
390
+ compatibility: "^0.0.0",
391
+ }),
392
+ __metadata("design:paramtypes", [core_1.ProcessContext])
393
+ ], AssetServerPlugin);
394
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../src/plugin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,159 @@
1
+ /// <reference types="node" />
2
+ /// <reference types="node" />
3
+ /// <reference types="node" />
4
+ import { AwsCredentialIdentity, AwsCredentialIdentityProvider } from "@aws-sdk/types";
5
+ import { AssetStorageStrategy } from "@deenruv/core";
6
+ import { Request } from "express";
7
+ import { Readable } from "node:stream";
8
+ import { AssetServerOptions } from "./types";
9
+ /**
10
+ * @description
11
+ * Configuration for connecting to AWS S3.
12
+ *
13
+ * @docsCategory core plugins/AssetServerPlugin
14
+ * @docsPage S3AssetStorageStrategy
15
+ */
16
+ export interface S3Config {
17
+ /**
18
+ * @description
19
+ * The credentials used to access your s3 account. You can supply either the access key ID & secret, or you can make use of a
20
+ * [shared credentials file](https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/loading-node-credentials-shared.html)
21
+ * To use a shared credentials file, import the `fromIni()` function from the "@aws-sdk/credential-provider-ini" or "@aws-sdk/credential-providers" package and supply
22
+ * the profile name (e.g. `{ profile: 'default' }`) as its argument.
23
+ */
24
+ credentials: AwsCredentialIdentity | AwsCredentialIdentityProvider;
25
+ /**
26
+ * @description
27
+ * The S3 bucket in which to store the assets. If it does not exist, it will be created on startup.
28
+ */
29
+ bucket: string;
30
+ /**
31
+ * @description
32
+ * Configuration object passed directly to the AWS SDK.
33
+ * S3.Types.ClientConfiguration can be used after importing aws-sdk.
34
+ * Using type `any` in order to avoid the need to include `aws-sdk` dependency in general.
35
+ */
36
+ nativeS3Configuration?: any;
37
+ /**
38
+ * @description
39
+ * Configuration object passed directly to the AWS SDK.
40
+ * ManagedUpload.ManagedUploadOptions can be used after importing aws-sdk.
41
+ * Using type `any` in order to avoid the need to include `aws-sdk` dependency in general.
42
+ */
43
+ nativeS3UploadConfiguration?: any;
44
+ }
45
+ /**
46
+ * @description
47
+ * Returns a configured instance of the {@link S3AssetStorageStrategy} which can then be passed to the {@link AssetServerOptions}
48
+ * `storageStrategyFactory` property.
49
+ *
50
+ * Before using this strategy, make sure you have the `@aws-sdk/client-s3` and `@aws-sdk/lib-storage` package installed:
51
+ *
52
+ * ```sh
53
+ * npm install \@aws-sdk/client-s3 \@aws-sdk/lib-storage
54
+ * ```
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * import { AssetServerPlugin, configureS3AssetStorage } from '\@deenruv/asset-server-plugin';
59
+ * import { DefaultAssetNamingStrategy } from '\@deenruv/core';
60
+ * import { fromEnv } from '\@aws-sdk/credential-providers';
61
+ *
62
+ * // ...
63
+ *
64
+ * plugins: [
65
+ * AssetServerPlugin.init({
66
+ * route: 'assets',
67
+ * assetUploadDir: path.join(__dirname, 'assets'),
68
+ * namingStrategy: new DefaultAssetNamingStrategy(),
69
+ * storageStrategyFactory: configureS3AssetStorage({
70
+ * bucket: 'my-s3-bucket',
71
+ * credentials: fromEnv(), // or any other credential provider
72
+ * nativeS3Configuration: {
73
+ * region: process.env.AWS_REGION,
74
+ * },
75
+ * }),
76
+ * }),
77
+ * ```
78
+ *
79
+ * ## Usage with MinIO
80
+ *
81
+ * Reference: [How to use AWS SDK for Javascript with MinIO Server](https://docs.min.io/docs/how-to-use-aws-sdk-for-javascript-with-minio-server.html)
82
+ *
83
+ * @example
84
+ * ```ts
85
+ * import { AssetServerPlugin, configureS3AssetStorage } from '\@deenruv/asset-server-plugin';
86
+ * import { DefaultAssetNamingStrategy } from '\@deenruv/core';
87
+ *
88
+ * // ...
89
+ *
90
+ * plugins: [
91
+ * AssetServerPlugin.init({
92
+ * route: 'assets',
93
+ * assetUploadDir: path.join(__dirname, 'assets'),
94
+ * namingStrategy: new DefaultAssetNamingStrategy(),
95
+ * storageStrategyFactory: configureS3AssetStorage({
96
+ * bucket: 'my-minio-bucket',
97
+ * credentials: {
98
+ * accessKeyId: process.env.MINIO_ACCESS_KEY_ID,
99
+ * secretAccessKey: process.env.MINIO_SECRET_ACCESS_KEY,
100
+ * },
101
+ * nativeS3Configuration: {
102
+ * endpoint: process.env.MINIO_ENDPOINT ?? 'http://localhost:9000',
103
+ * forcePathStyle: true,
104
+ * signatureVersion: 'v4',
105
+ * // The `region` is required by the AWS SDK even when using MinIO,
106
+ * // so we just use a dummy value here.
107
+ * region: 'eu-west-1',
108
+ * },
109
+ * }),
110
+ * }),
111
+ * ```
112
+ * @docsCategory core plugins/AssetServerPlugin
113
+ * @docsPage S3AssetStorageStrategy
114
+ */
115
+ export declare function configureS3AssetStorage(s3Config: S3Config): (options: AssetServerOptions) => S3AssetStorageStrategy;
116
+ /**
117
+ * @description
118
+ * An {@link AssetStorageStrategy} which uses [Amazon S3](https://aws.amazon.com/s3/) object storage service.
119
+ * To us this strategy you must first have access to an AWS account.
120
+ * See their [getting started guide](https://aws.amazon.com/s3/getting-started/) for how to get set up.
121
+ *
122
+ * Before using this strategy, make sure you have the `@aws-sdk/client-s3` and `@aws-sdk/lib-storage` package installed:
123
+ *
124
+ * ```sh
125
+ * npm install \@aws-sdk/client-s3 \@aws-sdk/lib-storage
126
+ * ```
127
+ *
128
+ * **Note:** Rather than instantiating this manually, use the {@link configureS3AssetStorage} function.
129
+ *
130
+ * ## Use with S3-compatible services (MinIO)
131
+ * This strategy will also work with any S3-compatible object storage solutions, such as [MinIO](https://min.io/).
132
+ * See the {@link configureS3AssetStorage} for an example with MinIO.
133
+ *
134
+ * @docsCategory asset-server-plugin
135
+ * @docsPage S3AssetStorageStrategy
136
+ * @docsWeight 0
137
+ */
138
+ export declare class S3AssetStorageStrategy implements AssetStorageStrategy {
139
+ private s3Config;
140
+ readonly toAbsoluteUrl: (request: Request, identifier: string) => string;
141
+ private AWS;
142
+ private libStorage;
143
+ private s3Client;
144
+ constructor(s3Config: S3Config, toAbsoluteUrl: (request: Request, identifier: string) => string);
145
+ init(): Promise<void>;
146
+ destroy?: (() => void | Promise<void>) | undefined;
147
+ writeFileFromBuffer(fileName: string, data: Buffer): Promise<string>;
148
+ writeFileFromStream(fileName: string, data: Readable): Promise<string>;
149
+ readFileToBuffer(identifier: string): Promise<Buffer>;
150
+ readFileToStream(identifier: string): Promise<Readable>;
151
+ private readFile;
152
+ private writeFile;
153
+ deleteFile(identifier: string): Promise<void>;
154
+ fileExists(fileName: string): Promise<boolean>;
155
+ private getObjectParams;
156
+ private ensureBucket;
157
+ private getCredentials;
158
+ private isCredentialsProfile;
159
+ }