@christian-wechsel/image-assets 0.0.0-stage → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +126 -2
- package/ReadMe.development.md +186 -0
- package/dist/cache/cache.d.ts +7 -0
- package/dist/cache/cache.d.ts.map +1 -0
- package/dist/cache/cache.js +15 -0
- package/dist/cache/cache.js.map +1 -0
- package/dist/image-resizer/image-resizer.d.ts +16 -0
- package/dist/image-resizer/image-resizer.d.ts.map +1 -0
- package/dist/image-resizer/image-resizer.js +30 -0
- package/dist/image-resizer/image-resizer.js.map +1 -0
- package/dist/image-storage/image-storage-disk.d.ts +16 -0
- package/dist/image-storage/image-storage-disk.d.ts.map +1 -0
- package/dist/image-storage/image-storage-disk.js +72 -0
- package/dist/image-storage/image-storage-disk.js.map +1 -0
- package/dist/image-storage/image-storage.d.ts +16 -0
- package/dist/image-storage/image-storage.d.ts.map +1 -0
- package/dist/image-storage/image-storage.js +37 -0
- package/dist/image-storage/image-storage.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/service/image-asset-service.d.ts +14 -0
- package/dist/service/image-asset-service.d.ts.map +1 -0
- package/dist/service/image-asset-service.js +70 -0
- package/dist/service/image-asset-service.js.map +1 -0
- package/dist/types/images.d.ts +21 -0
- package/dist/types/images.d.ts.map +1 -0
- package/dist/types/images.js +14 -0
- package/dist/types/images.js.map +1 -0
- package/package.json +49 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Christian Wechsel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,127 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @christian-wechsel/image-assets
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
TypeScript service for resizing, storing, caching, and retrieving image assets.
|
|
4
|
+
|
|
5
|
+
`image-assets` orchestrates image handling for an application: it decides which device/scale variants of an image need to exist, resizes them via a pluggable `ImageResizer`, and persists them through a pluggable `ImageStorage` backend with an in-memory cache in front of it.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Variant-based uploads**: Define a set of `DeviceImageType`s (e.g. `DESKTOP`/`MAIN`, `DESKTOP`/`PREVIEW`) and let `ImageAssetService` generate and store all required variants from a single uploaded image.
|
|
12
|
+
- **Skip redundant work**: Before resizing, the service checks whether a variant already exists in storage and skips it if so.
|
|
13
|
+
- **Pluggable storage**: `ImageStorage` is an abstract base class with built-in in-memory caching (reads and existence checks hit the cache first). Ships with a disk-based implementation (`ImageStorageDisk`); implement your own for GCS, S3, etc.
|
|
14
|
+
- **Pluggable resizing**: `ImageResizer` delegates the actual resizing work to an HTTP service (see [Image Resizer prerequisite](#prerequisite-image-resizer-service) below).
|
|
15
|
+
- **ESM / TypeScript**: Ships as compiled ESM with type declarations.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
```shell
|
|
22
|
+
npm install @christian-wechsel/image-assets
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Prerequisite: Image Resizer Service
|
|
28
|
+
|
|
29
|
+
`ImageAssetService` does not resize images itself. It delegates this work over HTTP to an `ImageResizer`, which expects a running service that exposes a `POST /resize` endpoint:
|
|
30
|
+
|
|
31
|
+
- Request: `POST {url}/resize?width={width}&height={height}&format={png|jpeg}`, with header `Authorization: Bearer {apiKey}` and the raw image bytes as the request body.
|
|
32
|
+
- Response: the resized image as binary data.
|
|
33
|
+
|
|
34
|
+
You need to deploy such a service yourself, for example as a **Google Cloud Run** service. You can use [`image-resizer`](https://github.com/ChristianWechsel/image-resizer) for this — it implements exactly this contract using [`sharp`](https://www.npmjs.com/package/sharp).
|
|
35
|
+
|
|
36
|
+
> **Note:** `image-resizer` is **not published on npm**. It is only available as source on GitHub: https://github.com/ChristianWechsel/image-resizer.git. Clone it, build it (`npm run build-prod`), and deploy it yourself (e.g. to Cloud Run) or implement a compatible service of your own.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
import {
|
|
44
|
+
ImageAssetService,
|
|
45
|
+
ImageResizer,
|
|
46
|
+
ImageStorageDisk,
|
|
47
|
+
Cache,
|
|
48
|
+
type DeviceImageType,
|
|
49
|
+
} from "@christian-wechsel/image-assets";
|
|
50
|
+
|
|
51
|
+
// 1. Configure the resizer client (points at your deployed image-resizer service)
|
|
52
|
+
const imageResizer = new ImageResizer({
|
|
53
|
+
url: "https://<your-image-resizer-cloud-run-url>",
|
|
54
|
+
apiKey: "<api-key>",
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// 2. Configure a storage backend (disk is built-in; implement ImageStorage for others)
|
|
58
|
+
const cache = new Cache<string, Buffer>();
|
|
59
|
+
const imageStorage = new ImageStorageDisk(cache, "./uploads");
|
|
60
|
+
|
|
61
|
+
// 3. Define the variants that should be generated for every uploaded image
|
|
62
|
+
const imageTypes: DeviceImageType[] = [
|
|
63
|
+
{ type: "DESKTOP", scale: "MAIN", format: "png" },
|
|
64
|
+
{ type: "DESKTOP", scale: "PREVIEW", format: "png" },
|
|
65
|
+
];
|
|
66
|
+
|
|
67
|
+
const imageAssetService = new ImageAssetService(
|
|
68
|
+
imageResizer,
|
|
69
|
+
imageStorage,
|
|
70
|
+
imageTypes,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// Upload an image; missing variants are resized and stored automatically
|
|
74
|
+
await imageAssetService.upload(imageBuffer, "photo.png");
|
|
75
|
+
|
|
76
|
+
// Download a specific variant (served from cache if available)
|
|
77
|
+
const { image, fileName } = await imageAssetService.download({
|
|
78
|
+
type: "DESKTOP",
|
|
79
|
+
scale: "MAIN",
|
|
80
|
+
format: "png",
|
|
81
|
+
filename: "photo.png",
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## API
|
|
88
|
+
|
|
89
|
+
| Export | Description |
|
|
90
|
+
| :--- | :--- |
|
|
91
|
+
| `ImageAssetService` | Orchestrates upload (resize + store missing variants) and download of image variants. |
|
|
92
|
+
| `ImageResizer` | HTTP client that calls an external image resizing service (see [prerequisite](#prerequisite-image-resizer-service)). |
|
|
93
|
+
| `ImageStorage` | Abstract base class for storage backends, with built-in caching. Extend it to support custom backends. |
|
|
94
|
+
| `ImageStorageDisk` | File-system-based `ImageStorage` implementation. |
|
|
95
|
+
| `Cache` | Simple in-memory key/value cache used in front of storage reads. |
|
|
96
|
+
| `DeviceImageType`, `ImageParams`, `StoredImage` | Types describing image variants and stored results. |
|
|
97
|
+
|
|
98
|
+
### Image variants
|
|
99
|
+
|
|
100
|
+
Each variant is described by:
|
|
101
|
+
|
|
102
|
+
- `type`: `"DESKTOP"` \| `"MOBILE"`
|
|
103
|
+
- `scale`: `"ORIGINAL"` \| `"MAIN"` \| `"PREVIEW"`
|
|
104
|
+
- `format`: `"png"` \| `"jpeg"`
|
|
105
|
+
|
|
106
|
+
Currently, target dimensions are defined for `DESKTOP`/`MAIN` (688×384) and `DESKTOP`/`PREVIEW` (320×180). Other combinations are stored without resizing.
|
|
107
|
+
|
|
108
|
+
### Implementing a custom `ImageStorage`
|
|
109
|
+
|
|
110
|
+
Extend `ImageStorage` and implement the four protected hooks:
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
class MyStorage extends ImageStorage {
|
|
114
|
+
protected uploadImageImpl(image: Buffer, params: ImageParams): Promise<void> { ... }
|
|
115
|
+
protected downloadImageImpl(params: ImageParams): Promise<StoredImage> { ... }
|
|
116
|
+
protected existsImpl(params: ImageParams): Promise<boolean> { ... }
|
|
117
|
+
protected listStorageContent(): Promise<ImageParams[]> { ... }
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`ImageStorage` takes care of caching reads/existence checks and writing through to the cache on upload.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# TypeScript Library for Google Cloud & NPM
|
|
2
|
+
|
|
3
|
+
A production-ready TypeScript library package pre-configured with strict TypeScript compilation, Jest testing (unit and integration), and automated CI/CD via Google Cloud Build and Terraform.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Strict TypeScript**: Configured with modern ESM settings (`tsconfig.json`, `tsconfig.prod.json`, `tsconfig.test.json`).
|
|
10
|
+
- **Jest Test Suites**: Split into fast unit tests (`*.test.unit.ts`) and integration tests (`*.test.int.ts`).
|
|
11
|
+
- **Dual CI/CD Pipeline (`cloudbuild.yaml`)**:
|
|
12
|
+
- **Internal Releases**: Pushes to the target branch (e.g., `main`) automatically publish to your private **GCP Artifact Registry** NPM repository.
|
|
13
|
+
- **Public Releases**: Pushing a version tag (e.g., `v1.0.0`) automatically publishes to **npmjs.org** using a token from **GCP Secret Manager**.
|
|
14
|
+
- **Infrastructure as Code (Terraform)**: Pre-configured Terraform code (`main.tf`, `variables.tf`) to manage Cloud Build triggers connected to your GitHub repository.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Project Structure
|
|
19
|
+
|
|
20
|
+
```text
|
|
21
|
+
├── bin/
|
|
22
|
+
│ └── clean.sh # Cleans dist/ build outputs
|
|
23
|
+
├── src/
|
|
24
|
+
│ ├── index.ts # Library entry point (public exports)
|
|
25
|
+
│ ├── math/ # Example domain module
|
|
26
|
+
│ │ ├── add.ts # Sample function
|
|
27
|
+
│ │ ├── add.testdata.ts # Test data fixtures
|
|
28
|
+
│ │ └── add.test.unit.ts # Unit test suite
|
|
29
|
+
│ └── integration/ # Integration test directory
|
|
30
|
+
│ └── add.test.int.ts # Integration test suite
|
|
31
|
+
├── cloudbuild.yaml # Cloud Build pipeline configuration
|
|
32
|
+
├── jest.config.mjs # Jest configuration with project suites
|
|
33
|
+
├── main.tf # Terraform Cloud Build triggers
|
|
34
|
+
├── package.json # Package manifest, scripts, and dependencies
|
|
35
|
+
├── provider.tf # Terraform Google provider config
|
|
36
|
+
├── terraform.tfvars.example # Example variable values for Terraform
|
|
37
|
+
├── tsconfig.json # Base TypeScript configuration
|
|
38
|
+
├── tsconfig.prod.json # Production build configuration
|
|
39
|
+
├── tsconfig.test.json # Test build configuration
|
|
40
|
+
└── variables.tf # Terraform variable definitions
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Getting Started
|
|
46
|
+
|
|
47
|
+
### 1. Install Dependencies
|
|
48
|
+
|
|
49
|
+
```shell
|
|
50
|
+
npm install
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 2. Configure Package Metadata
|
|
54
|
+
|
|
55
|
+
Open `package.json` and adjust:
|
|
56
|
+
|
|
57
|
+
- `"name"`: Set your desired package name (e.g., `@your-scope/my-library`).
|
|
58
|
+
- `"description"`: Brief summary of the library.
|
|
59
|
+
- `"author"`: Your name and email.
|
|
60
|
+
- `"repository"`: Git repository URL.
|
|
61
|
+
|
|
62
|
+
Also update the [LICENSE](LICENSE) file with the current year and your name or organization.
|
|
63
|
+
|
|
64
|
+
### 3. Setup GCP Cloud Build Triggers (Terraform)
|
|
65
|
+
|
|
66
|
+
This repository includes Terraform configuration to create the Cloud Build triggers that run your CI/CD pipeline on branch pushes and tag releases.
|
|
67
|
+
|
|
68
|
+
#### Prerequisite: Cloud Build 2nd Gen GitHub Connection
|
|
69
|
+
|
|
70
|
+
For Cloud Build triggers to respond to Git push events, your GitHub account and repository must be connected to Google Cloud Build (2nd Gen). This connection must be authorized in GitHub:
|
|
71
|
+
|
|
72
|
+
1. In **Google Cloud Console** > **Cloud Build** > **Repositories** (under **2nd gen**), click **Create host connection**.
|
|
73
|
+
2. Follow the prompt to install and authorize the **Google Cloud Build GitHub App** on your GitHub account or organization.
|
|
74
|
+
3. Click **Link repository** to connect your specific GitHub repository to this host connection.
|
|
75
|
+
|
|
76
|
+
#### Provisioning the Triggers
|
|
77
|
+
|
|
78
|
+
1. **Copy the example variables file:**
|
|
79
|
+
|
|
80
|
+
```shell
|
|
81
|
+
cp terraform.tfvars.example terraform.tfvars
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
2. **Fill in your environment details in `terraform.tfvars`:**
|
|
85
|
+
- `project_id`: GCP Project ID.
|
|
86
|
+
- `location`: GCP region (e.g., `europe-west3`).
|
|
87
|
+
- `github_connection_name`: Name of the Cloud Build 2nd Gen host connection.
|
|
88
|
+
- `github_repo_name`: Name of the linked repository.
|
|
89
|
+
- `target_branch`: Git branch that triggers CI/CD builds on push (e.g., `main`).
|
|
90
|
+
- `account_id`: Service Account ID for Cloud Build.
|
|
91
|
+
- `gcp_repository_name`: Repository identifier in Cloud Build (`<github_owner>-<github_repo_name>`).
|
|
92
|
+
- `npm_scope`: NPM scope (e.g., `@my-org`).
|
|
93
|
+
- `npm_repository_id`: Artifact Registry repository ID for NPM packages.
|
|
94
|
+
- `npm_secret_name`: Secret Manager secret name for the NPM token.
|
|
95
|
+
|
|
96
|
+
3. **Deploy the triggers:**
|
|
97
|
+
|
|
98
|
+
```shell
|
|
99
|
+
terraform init
|
|
100
|
+
terraform apply
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
> **Note:** `terraform.tfvars` contains sensitive environment identifiers and is ignored by `.gitignore`. Do not commit this file.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Local Development Scripts
|
|
108
|
+
|
|
109
|
+
| Command | Description |
|
|
110
|
+
| :--- | :--- |
|
|
111
|
+
| `npm run build` | Compiles TypeScript using `tsconfig.json` into `dist/`. |
|
|
112
|
+
| `npm run build-prod` | Produces a clean production build (`tsconfig.prod.json`). |
|
|
113
|
+
| `npm test` | Builds test configuration and runs all unit and integration tests. |
|
|
114
|
+
| `npm run test:unit` | Runs only unit tests (`*.test.unit.ts`). |
|
|
115
|
+
| `npm run test:int` | Runs only integration tests (`*.test.int.ts`). |
|
|
116
|
+
| `npm run clean` | Removes the `dist/` build directory. |
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## CI/CD & Publishing Workflow
|
|
121
|
+
|
|
122
|
+
The included [cloudbuild.yaml](cloudbuild.yaml) pipeline automates testing and deployment:
|
|
123
|
+
|
|
124
|
+
### 1. Internal Branch Releases (GCP Artifact Registry)
|
|
125
|
+
|
|
126
|
+
Every push to your target branch (`main`):
|
|
127
|
+
|
|
128
|
+
1. Runs `npm audit --audit-level=high` for vulnerability scanning.
|
|
129
|
+
2. Runs `npm ci` and `npm test` across all suites.
|
|
130
|
+
3. Automatically authenticates using Cloud Build service account credentials.
|
|
131
|
+
4. Publishes an internal package version to your private GCP Artifact Registry.
|
|
132
|
+
|
|
133
|
+
### 2. Public Releases (npmjs.org)
|
|
134
|
+
|
|
135
|
+
When you are ready to publish a release to public npm:
|
|
136
|
+
|
|
137
|
+
1. Update the version in `package.json`:
|
|
138
|
+
|
|
139
|
+
```shell
|
|
140
|
+
npm version patch # or minor, major
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
2. Push the commit and the version tag:
|
|
144
|
+
|
|
145
|
+
```shell
|
|
146
|
+
git push origin main --tags
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
3. Cloud Build detects the `v*` tag, retrieves the `NPM_TOKEN` secret from Secret Manager, and publishes the package with public access:
|
|
150
|
+
|
|
151
|
+
```shell
|
|
152
|
+
npm publish --access public
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Consuming this Library
|
|
158
|
+
|
|
159
|
+
### From GCP Artifact Registry (Internal)
|
|
160
|
+
|
|
161
|
+
In downstream projects, configure `.npmrc`:
|
|
162
|
+
|
|
163
|
+
```npmrc
|
|
164
|
+
@your-scope:registry=https://<REGION>-npm.pkg.dev/<PROJECT_ID>/<REPOSITORY_ID>/
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Authenticate your local environment:
|
|
168
|
+
|
|
169
|
+
```shell
|
|
170
|
+
npx google-artifactregistry-auth
|
|
171
|
+
npm install @your-scope/my-library
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### From npmjs.org (Public)
|
|
175
|
+
|
|
176
|
+
```shell
|
|
177
|
+
npm install @your-scope/my-library
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## License
|
|
183
|
+
|
|
184
|
+
[MIT](LICENSE)
|
|
185
|
+
|
|
186
|
+
> **Note:** Update the [LICENSE](LICENSE) file manually with the current year and your name or organization (replace `<YEAR>` and `<AUTHOR_OR_ORGANIZATION>`).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache/cache.ts"],"names":[],"mappings":"AAAA,qBAAa,KAAK,CAAC,GAAG,SAAS,MAAM,GAAG,MAAM,EAAE,KAAK;IACnD,OAAO,CAAC,KAAK,CAA8B;IAE3C,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,KAAK,GAAG,SAAS,CAE/B;IAED,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAEhC;IAED,GAAG,CAAC,GAAG,EAAE,GAAG,GAAG,OAAO,CAErB;CACF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export class Cache {
|
|
2
|
+
constructor() {
|
|
3
|
+
this.store = new Map();
|
|
4
|
+
}
|
|
5
|
+
get(key) {
|
|
6
|
+
return this.store.get(key);
|
|
7
|
+
}
|
|
8
|
+
set(key, value) {
|
|
9
|
+
this.store.set(key, value);
|
|
10
|
+
}
|
|
11
|
+
has(key) {
|
|
12
|
+
return this.store.has(key);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cache/cache.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,KAAK;IAAlB;QACU,UAAK,GAAoB,IAAI,GAAG,EAAE,CAAC;IAa7C,CAAC;IAXC,GAAG,CAAC,GAAQ;QACV,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,GAAG,CAAC,GAAQ,EAAE,KAAY;QACxB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC7B,CAAC;IAED,GAAG,CAAC,GAAQ;QACV,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type ImageSizeProperties = {
|
|
2
|
+
width: number;
|
|
3
|
+
height: number;
|
|
4
|
+
format: "png" | "jpeg";
|
|
5
|
+
};
|
|
6
|
+
export declare class ImageResizer {
|
|
7
|
+
private options;
|
|
8
|
+
private readonly pathResize;
|
|
9
|
+
constructor(options: {
|
|
10
|
+
url: string;
|
|
11
|
+
apiKey: string;
|
|
12
|
+
});
|
|
13
|
+
resizeImage(image: BodyInit, options: ImageSizeProperties): Promise<Buffer>;
|
|
14
|
+
private getUrlWithParams;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=image-resizer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-resizer.d.ts","sourceRoot":"","sources":["../../src/image-resizer/image-resizer.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,mBAAmB,GAAG;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;CACxB,CAAC;AAEF,qBAAa,YAAY;IAGX,OAAO,CAAC,OAAO;IAF3B,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAa;IAExC,YAAoB,OAAO,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,EAAI;IAEhE,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,MAAM,CAAC,CAe1E;IAED,OAAO,CAAC,gBAAgB;CAOzB"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export class ImageResizer {
|
|
2
|
+
constructor(options) {
|
|
3
|
+
this.options = options;
|
|
4
|
+
this.pathResize = "/resize";
|
|
5
|
+
}
|
|
6
|
+
resizeImage(image, options) {
|
|
7
|
+
return fetch(this.getUrlWithParams(this.pathResize, options), {
|
|
8
|
+
method: "POST",
|
|
9
|
+
headers: {
|
|
10
|
+
"Content-Type": "image/png",
|
|
11
|
+
Authorization: `Bearer ${this.options.apiKey}`,
|
|
12
|
+
},
|
|
13
|
+
body: image,
|
|
14
|
+
})
|
|
15
|
+
.then((response) => {
|
|
16
|
+
return response.arrayBuffer();
|
|
17
|
+
})
|
|
18
|
+
.then((arrayBuffer) => {
|
|
19
|
+
return Buffer.from(arrayBuffer);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
getUrlWithParams(path, options) {
|
|
23
|
+
const url = new URL(path, this.options.url);
|
|
24
|
+
url.searchParams.append("width", options.width.toString());
|
|
25
|
+
url.searchParams.append("height", options.height.toString());
|
|
26
|
+
url.searchParams.append("format", options.format);
|
|
27
|
+
return url.toString();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=image-resizer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-resizer.js","sourceRoot":"","sources":["../../src/image-resizer/image-resizer.ts"],"names":[],"mappings":"AAMA,MAAM,OAAO,YAAY;IAGvB,YAAoB,OAAwC;QAAxC,YAAO,GAAP,OAAO,CAAiC;QAF3C,eAAU,GAAG,SAAS,CAAC;IAEuB,CAAC;IAEhE,WAAW,CAAC,KAAe,EAAE,OAA4B;QACvD,OAAO,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,UAAU,EAAE,OAAO,CAAC,EAAE;YAC5D,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,WAAW;gBAC3B,aAAa,EAAE,UAAU,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE;aAC/C;YACD,IAAI,EAAE,KAAK;SACZ,CAAC;aACC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE;YACjB,OAAO,QAAQ,CAAC,WAAW,EAAE,CAAC;QAChC,CAAC,CAAC;aACD,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE;YACpB,OAAO,MAAM,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAClC,CAAC,CAAC,CAAC;IACP,CAAC;IAEO,gBAAgB,CAAC,IAAY,EAAE,OAA4B;QACjE,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5C,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC3D,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC7D,GAAG,CAAC,YAAY,CAAC,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QAClD,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;CACF"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Cache } from "../cache/cache.js";
|
|
2
|
+
import { type ImageParams, type StoredImage } from "../types/images.js";
|
|
3
|
+
import { ImageStorage } from "./image-storage.js";
|
|
4
|
+
export declare class ImageStorageDisk extends ImageStorage {
|
|
5
|
+
private folder;
|
|
6
|
+
constructor(cache: Cache<string, Buffer<ArrayBufferLike>>, folder: string);
|
|
7
|
+
protected uploadImageImpl(image: Buffer<ArrayBufferLike>, params: ImageParams): Promise<void>;
|
|
8
|
+
protected downloadImageImpl(params: ImageParams): Promise<StoredImage>;
|
|
9
|
+
protected listStorageContent(): Promise<ImageParams[]>;
|
|
10
|
+
protected existsImpl(params: ImageParams): Promise<boolean>;
|
|
11
|
+
private getFolderPath;
|
|
12
|
+
private createFolderIfNotExists;
|
|
13
|
+
private getImagePath;
|
|
14
|
+
private readDir;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=image-storage-disk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-storage-disk.d.ts","sourceRoot":"","sources":["../../src/image-storage/image-storage-disk.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAIL,KAAK,WAAW,EAChB,KAAK,WAAW,EACjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,qBAAa,gBAAiB,SAAQ,YAAY;IAG9C,OAAO,CAAC,MAAM;IAFhB,YACE,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,eAAe,CAAC,CAAC,EACrC,MAAM,EAAE,MAAM,EAIvB;IAED,SAAS,CAAC,eAAe,CACvB,KAAK,EAAE,MAAM,CAAC,eAAe,CAAC,EAC9B,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,IAAI,CAAC,CAKf;IAED,SAAS,CAAC,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAOrE;IAED,SAAS,CAAC,kBAAkB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAErD;IAED,SAAS,CAAC,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAI1D;IAED,OAAO,CAAC,aAAa;IAIrB,OAAO,CAAC,uBAAuB;IAM/B,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,OAAO;CAgChB"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { existsSync, lstatSync, mkdirSync, readdirSync } from "fs";
|
|
2
|
+
import { readFile, writeFile } from "fs/promises";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { isImageParams, isImageScale, isImageType, } from "../types/images.js";
|
|
5
|
+
import { ImageStorage } from "./image-storage.js";
|
|
6
|
+
export class ImageStorageDisk extends ImageStorage {
|
|
7
|
+
constructor(cache, folder) {
|
|
8
|
+
super(cache);
|
|
9
|
+
this.folder = folder;
|
|
10
|
+
mkdirSync(this.folder, { recursive: true });
|
|
11
|
+
}
|
|
12
|
+
uploadImageImpl(image, params) {
|
|
13
|
+
const pathFolder = this.getFolderPath(params);
|
|
14
|
+
this.createFolderIfNotExists(pathFolder);
|
|
15
|
+
const pathImage = this.getImagePath(pathFolder, params.filename);
|
|
16
|
+
return writeFile(pathImage, image);
|
|
17
|
+
}
|
|
18
|
+
downloadImageImpl(params) {
|
|
19
|
+
const pathFolder = this.getFolderPath(params);
|
|
20
|
+
const pathImage = this.getImagePath(pathFolder, params.filename);
|
|
21
|
+
return readFile(pathImage).then((image) => ({
|
|
22
|
+
image,
|
|
23
|
+
fileName: params.filename,
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
listStorageContent() {
|
|
27
|
+
return Promise.resolve(this.readDir(this.folder));
|
|
28
|
+
}
|
|
29
|
+
existsImpl(params) {
|
|
30
|
+
const pathFolder = this.getFolderPath(params);
|
|
31
|
+
const pathImage = this.getImagePath(pathFolder, params.filename);
|
|
32
|
+
return Promise.resolve(existsSync(pathImage));
|
|
33
|
+
}
|
|
34
|
+
getFolderPath(params) {
|
|
35
|
+
return join(this.folder, params.type, params.scale);
|
|
36
|
+
}
|
|
37
|
+
createFolderIfNotExists(path) {
|
|
38
|
+
if (!existsSync(path)) {
|
|
39
|
+
mkdirSync(path, { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
getImagePath(folder, filename) {
|
|
43
|
+
return join(folder, filename);
|
|
44
|
+
}
|
|
45
|
+
readDir(path, parent = {}) {
|
|
46
|
+
const items = [];
|
|
47
|
+
const folderContent = readdirSync(path);
|
|
48
|
+
for (const item of folderContent) {
|
|
49
|
+
const itemPath = join(path, item);
|
|
50
|
+
const stat = lstatSync(itemPath);
|
|
51
|
+
const isDirectory = stat.isDirectory();
|
|
52
|
+
const isFile = stat.isFile();
|
|
53
|
+
if (isDirectory) {
|
|
54
|
+
if (isImageType(item)) {
|
|
55
|
+
parent.type = item;
|
|
56
|
+
}
|
|
57
|
+
if (isImageScale(item)) {
|
|
58
|
+
parent.scale = item;
|
|
59
|
+
}
|
|
60
|
+
items.push(...this.readDir(itemPath, { ...parent }));
|
|
61
|
+
}
|
|
62
|
+
if (isFile) {
|
|
63
|
+
parent.filename = item;
|
|
64
|
+
if (isImageParams(parent)) {
|
|
65
|
+
items.push({ ...parent });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return items;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=image-storage-disk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-storage-disk.js","sourceRoot":"","sources":["../../src/image-storage/image-storage-disk.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Cache } from "../cache/cache.js";
|
|
2
|
+
import { type ImageParams, type StoredImage } from "../types/images.js";
|
|
3
|
+
export declare abstract class ImageStorage {
|
|
4
|
+
private cache;
|
|
5
|
+
constructor(cache: Cache<string, Buffer<ArrayBufferLike>>);
|
|
6
|
+
uploadImage(image: Buffer<ArrayBufferLike>, params: ImageParams): Promise<void>;
|
|
7
|
+
downloadImage(params: ImageParams): Promise<StoredImage>;
|
|
8
|
+
initCache(): Promise<void>;
|
|
9
|
+
exists(params: ImageParams): Promise<boolean>;
|
|
10
|
+
protected abstract uploadImageImpl(image: Buffer<ArrayBufferLike>, params: ImageParams): Promise<void>;
|
|
11
|
+
protected abstract downloadImageImpl(params: ImageParams): Promise<StoredImage>;
|
|
12
|
+
protected abstract existsImpl(params: ImageParams): Promise<boolean>;
|
|
13
|
+
protected abstract listStorageContent(): Promise<ImageParams[]>;
|
|
14
|
+
protected getPath(params: ImageParams): string;
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=image-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-storage.d.ts","sourceRoot":"","sources":["../../src/image-storage/image-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,MAAM,oBAAoB,CAAC;AAExE,8BAAsB,YAAY;IACpB,OAAO,CAAC,KAAK;IAAzB,YAAoB,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,eAAe,CAAC,CAAC,EAAI;IAErE,WAAW,CACT,KAAK,EAAE,MAAM,CAAC,eAAe,CAAC,EAC9B,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,IAAI,CAAC,CAIf;IAED,aAAa,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAUvD;IAED,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,CAMzB;IAED,MAAM,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAK5C;IAED,SAAS,CAAC,QAAQ,CAAC,eAAe,CAChC,KAAK,EAAE,MAAM,CAAC,eAAe,CAAC,EAC9B,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjB,SAAS,CAAC,QAAQ,CAAC,iBAAiB,CAClC,MAAM,EAAE,WAAW,GAClB,OAAO,CAAC,WAAW,CAAC,CAAC;IAExB,SAAS,CAAC,QAAQ,CAAC,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAErE,SAAS,CAAC,QAAQ,CAAC,kBAAkB,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;IAEhE,SAAS,CAAC,OAAO,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAE7C;CACF"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import {} from "../types/images.js";
|
|
2
|
+
export class ImageStorage {
|
|
3
|
+
constructor(cache) {
|
|
4
|
+
this.cache = cache;
|
|
5
|
+
}
|
|
6
|
+
uploadImage(image, params) {
|
|
7
|
+
return this.uploadImageImpl(image, params).then(() => {
|
|
8
|
+
this.cache.set(this.getPath(params), image);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
downloadImage(params) {
|
|
12
|
+
const path = this.getPath(params);
|
|
13
|
+
const cached = this.cache.get(path);
|
|
14
|
+
if (cached) {
|
|
15
|
+
return Promise.resolve({ image: cached, fileName: params.filename });
|
|
16
|
+
}
|
|
17
|
+
return this.downloadImageImpl(params).then((storedImage) => {
|
|
18
|
+
this.cache.set(path, storedImage.image);
|
|
19
|
+
return storedImage;
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
initCache() {
|
|
23
|
+
return this.listStorageContent().then((items) => {
|
|
24
|
+
return Promise.allSettled(items.map((item) => this.downloadImage(item))).then(() => { });
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
exists(params) {
|
|
28
|
+
if (this.cache.has(this.getPath(params))) {
|
|
29
|
+
return Promise.resolve(true);
|
|
30
|
+
}
|
|
31
|
+
return this.existsImpl(params);
|
|
32
|
+
}
|
|
33
|
+
getPath(params) {
|
|
34
|
+
return `/${params.type}/${params.scale}/${params.filename}`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=image-storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-storage.js","sourceRoot":"","sources":["../../src/image-storage/image-storage.ts"],"names":[],"mappings":"AACA,OAAO,EAAsC,MAAM,oBAAoB,CAAC;AAExE,MAAM,OAAgB,YAAY;IAChC,YAAoB,KAA6C;qBAA7C,KAAK;IAA2C,CAAC;IAErE,WAAW,CACT,KAA8B,EAC9B,MAAmB;QAEnB,OAAO,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE;YACnD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;QAC9C,CAAC,CAAC,CAAC;IACL,CAAC;IAED,aAAa,CAAC,MAAmB;QAC/B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;QAClC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,MAAM,EAAE,CAAC;YACX,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;QACvE,CAAC;QACD,OAAO,IAAI,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE;YACzD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC;YACxC,OAAO,WAAW,CAAC;QACrB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,SAAS;QACP,OAAO,IAAI,CAAC,kBAAkB,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;YAC9C,OAAO,OAAO,CAAC,UAAU,CACvB,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,CAC9C,CAAC,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACnB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,CAAC,MAAmB;QACxB,IAAI,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC;YACzC,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QAC/B,CAAC;QACD,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;IACjC,CAAC;IAeS,OAAO,CAAC,MAAmB;QACnC,OAAO,IAAI,MAAM,CAAC,IAAI,IAAI,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC9D,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,kCAAkC,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ImageResizer } from "../image-resizer/image-resizer.js";
|
|
2
|
+
import type { ImageStorage } from "../image-storage/image-storage.js";
|
|
3
|
+
import type { DeviceImageType, ImageParams, StoredImage } from "../types/images.js";
|
|
4
|
+
export declare class ImageAssetService {
|
|
5
|
+
private imageResizer;
|
|
6
|
+
private imageStorage;
|
|
7
|
+
private readonly imageTypes;
|
|
8
|
+
constructor(imageResizer: ImageResizer, imageStorage: ImageStorage, imageTypes: DeviceImageType[]);
|
|
9
|
+
upload(image: Buffer, originalName: string): Promise<void>;
|
|
10
|
+
download(params: ImageParams): Promise<StoredImage>;
|
|
11
|
+
private factoryResizeImage;
|
|
12
|
+
private resizeImage;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=image-asset-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-asset-service.d.ts","sourceRoot":"","sources":["../../src/service/image-asset-service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EAEb,MAAM,mCAAmC,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AACtE,OAAO,KAAK,EACV,eAAe,EACf,WAAW,EACX,WAAW,EACZ,MAAM,oBAAoB,CAAC;AAE5B,qBAAa,iBAAiB;IAE1B,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,YAAY;IACpB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAH7B,YACU,YAAY,EAAE,YAAY,EAC1B,YAAY,EAAE,YAAY,EACjB,UAAU,EAAE,eAAe,EAAE,EAC5C;IAEJ,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAoCzD;IAED,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAElD;IAED,OAAO,CAAC,kBAAkB;IAiC1B,OAAO,CAAC,WAAW;CAMpB"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
export class ImageAssetService {
|
|
2
|
+
constructor(imageResizer, imageStorage, imageTypes) {
|
|
3
|
+
this.imageResizer = imageResizer;
|
|
4
|
+
this.imageStorage = imageStorage;
|
|
5
|
+
this.imageTypes = imageTypes;
|
|
6
|
+
}
|
|
7
|
+
upload(image, originalName) {
|
|
8
|
+
return Promise.all(this.imageTypes
|
|
9
|
+
.map((imageType) => {
|
|
10
|
+
return {
|
|
11
|
+
imageParam: {
|
|
12
|
+
...imageType,
|
|
13
|
+
filename: originalName,
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
})
|
|
17
|
+
.map(({ imageParam }) => {
|
|
18
|
+
return this.imageStorage.exists(imageParam).then((exists) => {
|
|
19
|
+
return { exists, imageParam };
|
|
20
|
+
});
|
|
21
|
+
}))
|
|
22
|
+
.then((exits) => {
|
|
23
|
+
return Promise.all(exits
|
|
24
|
+
.filter(({ exists }) => {
|
|
25
|
+
return !exists;
|
|
26
|
+
})
|
|
27
|
+
.map(({ imageParam }) => {
|
|
28
|
+
return this.factoryResizeImage(imageParam)(image).then((resizedImage) => {
|
|
29
|
+
return this.imageStorage.uploadImage(resizedImage, imageParam);
|
|
30
|
+
});
|
|
31
|
+
}));
|
|
32
|
+
})
|
|
33
|
+
.then(() => { });
|
|
34
|
+
}
|
|
35
|
+
download(params) {
|
|
36
|
+
return this.imageStorage.downloadImage(params);
|
|
37
|
+
}
|
|
38
|
+
factoryResizeImage(params) {
|
|
39
|
+
let func = (image) => Promise.resolve(image);
|
|
40
|
+
if (params.type === "DESKTOP") {
|
|
41
|
+
switch (params.scale) {
|
|
42
|
+
case "MAIN":
|
|
43
|
+
func = (image) => {
|
|
44
|
+
return this.resizeImage(image, {
|
|
45
|
+
width: 688,
|
|
46
|
+
height: 384,
|
|
47
|
+
format: params.format,
|
|
48
|
+
});
|
|
49
|
+
};
|
|
50
|
+
break;
|
|
51
|
+
case "PREVIEW":
|
|
52
|
+
func = (image) => {
|
|
53
|
+
return this.resizeImage(image, {
|
|
54
|
+
width: 320,
|
|
55
|
+
height: 180,
|
|
56
|
+
format: params.format,
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else if (params.type === "MOBILE") {
|
|
63
|
+
}
|
|
64
|
+
return func;
|
|
65
|
+
}
|
|
66
|
+
resizeImage(image, options) {
|
|
67
|
+
return this.imageResizer.resizeImage(new Uint8Array(image), options);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=image-asset-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-asset-service.js","sourceRoot":"","sources":["../../src/service/image-asset-service.ts"],"names":[],"mappings":"AAWA,MAAM,OAAO,iBAAiB;IAC5B,YACU,YAA0B,EAC1B,YAA0B,EACjB,UAA6B;4BAFtC,YAAY;4BACZ,YAAY;0BACH,UAAU;IAC1B,CAAC;IAEJ,MAAM,CAAC,KAAa,EAAE,YAAoB;QACxC,OAAO,OAAO,CAAC,GAAG,CAChB,IAAI,CAAC,UAAU;aACZ,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE;YACjB,OAAO;gBACL,UAAU,EAAE;oBACV,GAAG,SAAS;oBACZ,QAAQ,EAAE,YAAY;iBACvB;aACF,CAAC;QACJ,CAAC,CAAC;aACD,GAAG,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE;YACtB,OAAO,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE;gBAC1D,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;YAChC,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CACL;aACE,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;YACd,OAAO,OAAO,CAAC,GAAG,CAChB,KAAK;iBACF,MAAM,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,EAAE;gBACrB,OAAO,CAAC,MAAM,CAAC;YACjB,CAAC,CAAC;iBACD,GAAG,CAAC,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE;gBACtB,OAAO,IAAI,CAAC,kBAAkB,CAAC,UAAU,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CACpD,CAAC,YAAY,EAAE,EAAE;oBACf,OAAO,IAAI,CAAC,YAAY,CAAC,WAAW,CAClC,YAAY,EACZ,UAAU,CACX,CAAC;gBACJ,CAAC,CACF,CAAC;YACJ,CAAC,CAAC,CACL,CAAC;QACJ,CAAC,CAAC;aACD,IAAI,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;IACpB,CAAC;IAED,QAAQ,CAAC,MAAmB;QAC1B,OAAO,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IACjD,CAAC;IAEO,kBAAkB,CACxB,MAAmB;QAEnB,IAAI,IAAI,GAAuC,CAAC,KAAa,EAAE,EAAE,CAC/D,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QAEzB,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC9B,QAAQ,MAAM,CAAC,KAAK,EAAE,CAAC;gBACrB,KAAK,MAAM;oBACT,IAAI,GAAG,CAAC,KAAa,EAAE,EAAE;wBACvB,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE;4BAC7B,KAAK,EAAE,GAAG;4BACV,MAAM,EAAE,GAAG;4BACX,MAAM,EAAE,MAAM,CAAC,MAAM;yBACtB,CAAC,CAAC;oBACL,CAAC,CAAC;oBACF,MAAM;gBACR,KAAK,SAAS;oBACZ,IAAI,GAAG,CAAC,KAAa,EAAE,EAAE;wBACvB,OAAO,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE;4BAC7B,KAAK,EAAE,GAAG;4BACV,MAAM,EAAE,GAAG;4BACX,MAAM,EAAE,MAAM,CAAC,MAAM;yBACtB,CAAC,CAAC;oBACL,CAAC,CAAC;oBACF,MAAM;YACV,CAAC;QACH,CAAC;aAAM,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;QACtC,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,WAAW,CACjB,KAAa,EACb,OAA4B;QAE5B,OAAO,IAAI,CAAC,YAAY,CAAC,WAAW,CAAC,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC;IACvE,CAAC;CACF"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ParsedPath } from "path";
|
|
2
|
+
type ImageType = "DESKTOP" | "MOBILE";
|
|
3
|
+
type ImageScale = "ORIGINAL" | "MAIN" | "PREVIEW";
|
|
4
|
+
export type DeviceImageType = {
|
|
5
|
+
type: ImageType;
|
|
6
|
+
scale: ImageScale;
|
|
7
|
+
format: "png" | "jpeg";
|
|
8
|
+
};
|
|
9
|
+
export type ImageStorageOptions = ParsedPath & DeviceImageType;
|
|
10
|
+
export declare function isImageType(value: any): value is ImageType;
|
|
11
|
+
export declare function isImageScale(value: any): value is ImageScale;
|
|
12
|
+
export type ImageParams = DeviceImageType & {
|
|
13
|
+
filename: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function isImageParams(obj: any): obj is ImageParams;
|
|
16
|
+
export type StoredImage = {
|
|
17
|
+
image: Buffer;
|
|
18
|
+
fileName: string;
|
|
19
|
+
};
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=images.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"images.d.ts","sourceRoot":"","sources":["../../src/types/images.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AAEvC,KAAK,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AACtC,KAAK,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,SAAS,CAAC;AAElD,MAAM,MAAM,eAAe,GAAG;IAC5B,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,KAAK,GAAG,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG,UAAU,GAAG,eAAe,CAAC;AAE/D,wBAAgB,WAAW,CAAC,KAAK,EAAE,GAAG,GAAG,KAAK,IAAI,SAAS,CAE1D;AACD,wBAAgB,YAAY,CAAC,KAAK,EAAE,GAAG,GAAG,KAAK,IAAI,UAAU,CAE5D;AAED,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG;IAC1C,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,wBAAgB,aAAa,CAAC,GAAG,EAAE,GAAG,GAAG,GAAG,IAAI,WAAW,CAQ1D;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function isImageType(value) {
|
|
2
|
+
return value === "DESKTOP" || value === "MOBILE";
|
|
3
|
+
}
|
|
4
|
+
export function isImageScale(value) {
|
|
5
|
+
return value === "ORIGINAL" || value === "MAIN" || value === "PREVIEW";
|
|
6
|
+
}
|
|
7
|
+
export function isImageParams(obj) {
|
|
8
|
+
return (obj &&
|
|
9
|
+
typeof obj.filename === "string" &&
|
|
10
|
+
obj.filename.length < 255 &&
|
|
11
|
+
isImageType(obj.type) &&
|
|
12
|
+
isImageScale(obj.scale));
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=images.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"images.js","sourceRoot":"","sources":["../../src/types/images.ts"],"names":[],"mappings":"AAaA,MAAM,UAAU,WAAW,CAAC,KAAU;IACpC,OAAO,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,QAAQ,CAAC;AACnD,CAAC;AACD,MAAM,UAAU,YAAY,CAAC,KAAU;IACrC,OAAO,KAAK,KAAK,UAAU,IAAI,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,SAAS,CAAC;AACzE,CAAC;AAMD,MAAM,UAAU,aAAa,CAAC,GAAQ;IACpC,OAAO,CACL,GAAG;QACH,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ;QAChC,GAAG,CAAC,QAAQ,CAAC,MAAM,GAAG,GAAG;QACzB,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC;QACrB,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC,CACxB,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@christian-wechsel/image-assets",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "TypeScript service for resizing, storing, caching, and retrieving image assets.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"test": "npm run build-test && jest",
|
|
8
|
+
"clean": "./bin/clean.sh",
|
|
9
|
+
"prebuild": "npm run clean",
|
|
10
|
+
"build": "tsc --project tsconfig.json",
|
|
11
|
+
"prebuild-test": "npm run clean",
|
|
12
|
+
"build-test": "tsc --project tsconfig.test.json",
|
|
13
|
+
"prebuild-prod": "npm run clean",
|
|
14
|
+
"build-prod": "tsc --project tsconfig.prod.json",
|
|
15
|
+
"prepublishOnly": "npm run build-prod",
|
|
16
|
+
"test:unit": "set NODE_ENV=test && npm run build-test && jest --selectProjects Unit",
|
|
17
|
+
"test:int": "set NODE_ENV=test && npm run build-test && jest --selectProjects Integration",
|
|
18
|
+
"debug": "node --inspect-brk dist/index.js",
|
|
19
|
+
"start": "node dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/ChristianWechsel/image-assets.git"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"image",
|
|
27
|
+
"images",
|
|
28
|
+
"image-assets",
|
|
29
|
+
"image-resizing",
|
|
30
|
+
"image-storage",
|
|
31
|
+
"asset-management",
|
|
32
|
+
"typescript"
|
|
33
|
+
],
|
|
34
|
+
"author": "Christian Wechsel <npm@christian-wechsel.com>",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"type": "module",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/ChristianWechsel/image-assets/issues"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/ChristianWechsel/image-assets#readme",
|
|
41
|
+
"files": [
|
|
42
|
+
"dist"
|
|
43
|
+
],
|
|
44
|
+
"types": "dist/index.d.ts",
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/jest": "^30.0.0",
|
|
47
|
+
"@types/node": "^26.6.6",
|
|
48
|
+
"jest": "^30.5.2",
|
|
49
|
+
"typescript": "^7.0.2"
|
|
50
|
+
}
|
|
51
|
+
}
|