@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 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
- # Temporary Holding Version
1
+ # @christian-wechsel/image-assets
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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,7 @@
1
+ export declare class Cache<KEY extends string | number, VALUE> {
2
+ private store;
3
+ get(key: KEY): VALUE | undefined;
4
+ set(key: KEY, value: VALUE): void;
5
+ has(key: KEY): boolean;
6
+ }
7
+ //# sourceMappingURL=cache.d.ts.map
@@ -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"}
@@ -0,0 +1,2 @@
1
+ export { ImageAssetService } from "./service/image-asset-service.js";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -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,2 @@
1
+ export { ImageAssetService } from "./service/image-asset-service.js";
2
+ //# sourceMappingURL=index.js.map
@@ -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.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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
+ }