@nixxie-cms/storage 1.0.1

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,23 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Nixxie International DMCC
4
+ Portions Copyright (c) 2023 Thinkmill Labs Pty Ltd and contributors
5
+ (this software is derived from the KeystoneJS project, https://keystonejs.com)
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ of this software and associated documentation files (the "Software"), to deal
9
+ in the Software without restriction, including without limitation the rights
10
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the Software is
12
+ furnished to do so, subject to the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be included in all
15
+ copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # @nixxie-cms/storage
2
+
3
+ Pluggable blob storage for Nixxie CMS. One interface, four drivers: local disk, AWS S3
4
+ (and S3-compatible services like Cloudflare R2, MinIO, DigitalOcean Spaces), Google Cloud
5
+ Storage, and Azure Blob Storage.
6
+
7
+ ```ts
8
+ import { config } from '@nixxie-cms/core'
9
+ import { createStorage } from '@nixxie-cms/storage'
10
+
11
+ export default config({
12
+ storage: createStorage({
13
+ driver: 's3',
14
+ bucket: 'my-bucket',
15
+ region: 'us-east-1',
16
+ }),
17
+ // ...
18
+ })
19
+ ```
20
+
21
+ Once configured it is available everywhere as `context.services.storage`:
22
+
23
+ ```ts
24
+ const file = await context.services.storage.put('avatars/1.png', buffer, {
25
+ contentType: 'image/png',
26
+ public: true,
27
+ })
28
+ const url = await context.services.storage.signedUrl('avatars/1.png', { expiresIn: 3600 })
29
+ ```
30
+
31
+ ## Drivers
32
+
33
+ | driver | extra dependency |
34
+ | ------- | ---------------- |
35
+ | `local` | none |
36
+ | `s3` | `@aws-sdk/client-s3`, `@aws-sdk/s3-request-presigner` |
37
+ | `gcs` | `@google-cloud/storage` |
38
+ | `azure` | `@azure/storage-blob` |
39
+
40
+ Driver dependencies are loaded lazily, so you only install the one you use.
41
+
42
+ ## API
43
+
44
+ `put`, `get`, `delete`, `has`, `url`, `signedUrl`, `list` — see `NixxieStorageService`.
@@ -0,0 +1,18 @@
1
+ import type { NixxiePutOptions, NixxieSignedUrlOptions, NixxieStorageService, NixxieStoredFile } from '@nixxie-cms/core';
2
+ import type { AzureStorageConfig } from "./types.js";
3
+ /** Azure Blob Storage backend. */
4
+ export declare class AzureStorage implements NixxieStorageService {
5
+ private config;
6
+ private prefix;
7
+ private container;
8
+ constructor(config: AzureStorageConfig);
9
+ private blob;
10
+ put(key: string, data: Buffer | Uint8Array | string, options?: NixxiePutOptions): Promise<NixxieStoredFile>;
11
+ get(key: string): Promise<Buffer | undefined>;
12
+ delete(key: string): Promise<void>;
13
+ has(key: string): Promise<boolean>;
14
+ url(key: string): string;
15
+ signedUrl(key: string, options?: NixxieSignedUrlOptions): Promise<string>;
16
+ list(prefix?: string): Promise<string[]>;
17
+ }
18
+ //# sourceMappingURL=AzureStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AzureStorage.d.ts","sourceRoot":"../../../src","sources":["AzureStorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,EACpB,gBAAgB,EACjB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAe;AAYjD,kCAAkC;AAClC,qBAAa,YAAa,YAAW,oBAAoB;IACvD,OAAO,CAAC,MAAM,CAAoB;IAClC,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,SAAS,CAAK;gBAEV,MAAM,EAAE,kBAAkB;IAQtC,OAAO,CAAC,IAAI;IAIN,GAAG,CACP,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,EAClC,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,gBAAgB,CAAC;IAStB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAS7C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIxC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAMlB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC;IAuBzE,IAAI,CAAC,MAAM,SAAK,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAQ3C"}
@@ -0,0 +1,18 @@
1
+ import type { NixxiePutOptions, NixxieSignedUrlOptions, NixxieStorageService, NixxieStoredFile } from '@nixxie-cms/core';
2
+ import type { GcsStorageConfig } from "./types.js";
3
+ /** Google Cloud Storage backend. */
4
+ export declare class GcsStorage implements NixxieStorageService {
5
+ private config;
6
+ private prefix;
7
+ private bucket;
8
+ constructor(config: GcsStorageConfig);
9
+ private file;
10
+ put(key: string, data: Buffer | Uint8Array | string, options?: NixxiePutOptions): Promise<NixxieStoredFile>;
11
+ get(key: string): Promise<Buffer | undefined>;
12
+ delete(key: string): Promise<void>;
13
+ has(key: string): Promise<boolean>;
14
+ url(key: string): string;
15
+ signedUrl(key: string, options?: NixxieSignedUrlOptions): Promise<string>;
16
+ list(prefix?: string): Promise<string[]>;
17
+ }
18
+ //# sourceMappingURL=GcsStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GcsStorage.d.ts","sourceRoot":"../../../src","sources":["GcsStorage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,EACpB,gBAAgB,EACjB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,gBAAgB,EAAE,mBAAe;AAY/C,oCAAoC;AACpC,qBAAa,UAAW,YAAW,oBAAoB;IACrD,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,MAAM,CAAK;gBAEP,MAAM,EAAE,gBAAgB;IAQpC,OAAO,CAAC,IAAI;IAIN,GAAG,CACP,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,EAClC,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,gBAAgB,CAAC;IAWtB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAU7C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAKxC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAMlB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC;IASzE,IAAI,CAAC,MAAM,SAAK,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAM3C"}
@@ -0,0 +1,20 @@
1
+ import type { NixxiePutOptions, NixxieSignedUrlOptions, NixxieStorageService, NixxieStoredFile } from '@nixxie-cms/core';
2
+ import type { LocalStorageConfig } from "./types.js";
3
+ /**
4
+ * Filesystem-backed storage. Good for development and single-node deployments.
5
+ * `signedUrl()` falls back to the plain public URL since the local disk cannot sign.
6
+ */
7
+ export declare class LocalStorage implements NixxieStorageService {
8
+ private baseDir;
9
+ private baseUrl;
10
+ constructor(config: LocalStorageConfig);
11
+ private path;
12
+ put(key: string, data: Buffer | Uint8Array | string, options?: NixxiePutOptions): Promise<NixxieStoredFile>;
13
+ get(key: string): Promise<Buffer | undefined>;
14
+ delete(key: string): Promise<void>;
15
+ has(key: string): Promise<boolean>;
16
+ url(key: string): string;
17
+ signedUrl(key: string, _options?: NixxieSignedUrlOptions): Promise<string>;
18
+ list(prefix?: string): Promise<string[]>;
19
+ }
20
+ //# sourceMappingURL=LocalStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LocalStorage.d.ts","sourceRoot":"../../../src","sources":["LocalStorage.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,EACpB,gBAAgB,EACjB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAe;AAEjD;;;GAGG;AACH,qBAAa,YAAa,YAAW,oBAAoB;IACvD,OAAO,CAAC,OAAO,CAAQ;IACvB,OAAO,CAAC,OAAO,CAAQ;gBAEX,MAAM,EAAE,kBAAkB;IAKtC,OAAO,CAAC,IAAI;IAWN,GAAG,CACP,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,EAClC,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,gBAAgB,CAAC;IAatB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAM7C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IASxC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IAIlB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC;IAI1E,IAAI,CAAC,MAAM,SAAK,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAmB3C"}
@@ -0,0 +1,20 @@
1
+ import type { NixxiePutOptions, NixxieSignedUrlOptions, NixxieStorageService, NixxieStoredFile } from '@nixxie-cms/core';
2
+ import type { S3StorageConfig } from "./types.js";
3
+ /** AWS S3 (and S3-compatible: R2, MinIO, Spaces) storage backend. */
4
+ export declare class S3Storage implements NixxieStorageService {
5
+ private config;
6
+ private prefix;
7
+ private client;
8
+ private s3;
9
+ private presigner;
10
+ constructor(config: S3StorageConfig);
11
+ private k;
12
+ put(key: string, data: Buffer | Uint8Array | string, options?: NixxiePutOptions): Promise<NixxieStoredFile>;
13
+ get(key: string): Promise<Buffer | undefined>;
14
+ delete(key: string): Promise<void>;
15
+ has(key: string): Promise<boolean>;
16
+ url(key: string): string;
17
+ signedUrl(key: string, options?: NixxieSignedUrlOptions): Promise<string>;
18
+ list(prefix?: string): Promise<string[]>;
19
+ }
20
+ //# sourceMappingURL=S3Storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"S3Storage.d.ts","sourceRoot":"../../../src","sources":["S3Storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,EACpB,gBAAgB,EACjB,MAAM,kBAAkB,CAAA;AACzB,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAe;AAkB9C,qEAAqE;AACrE,qBAAa,SAAU,YAAW,oBAAoB;IACpD,OAAO,CAAC,MAAM,CAAiB;IAC/B,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,MAAM,CAAoC;IAClD,OAAO,CAAC,EAAE,CAAU;IACpB,OAAO,CAAC,SAAS,CAAiB;gBAEtB,MAAM,EAAE,eAAe;IAiBnC,OAAO,CAAC,CAAC;IAIH,GAAG,CACP,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,EAClC,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,gBAAgB,CAAC;IAetB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAa7C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMlC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAWxC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM;IASlB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,sBAAsB,GAAG,OAAO,CAAC,MAAM,CAAC;IAczE,IAAI,CAAC,MAAM,SAAK,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;CAY3C"}
@@ -0,0 +1,10 @@
1
+ import type { StorageConfig } from "./types.js";
2
+ import { AzureStorage } from "./AzureStorage.js";
3
+ import { GcsStorage } from "./GcsStorage.js";
4
+ import { LocalStorage } from "./LocalStorage.js";
5
+ import { S3Storage } from "./S3Storage.js";
6
+ export declare function createStorage(config: StorageConfig): LocalStorage | S3Storage | GcsStorage | AzureStorage;
7
+ export { LocalStorage, S3Storage, GcsStorage, AzureStorage };
8
+ export type { StorageConfig, LocalStorageConfig, S3StorageConfig, GcsStorageConfig, AzureStorageConfig, } from "./types.js";
9
+ export type { NixxieStorageService, NixxieStoredFile, NixxiePutOptions, NixxieSignedUrlOptions, } from '@nixxie-cms/core';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"../../../src","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,mBAAe;AAC5C,OAAO,EAAE,YAAY,EAAE,0BAAsB;AAC7C,OAAO,EAAE,UAAU,EAAE,wBAAoB;AACzC,OAAO,EAAE,YAAY,EAAE,0BAAsB;AAC7C,OAAO,EAAE,SAAS,EAAE,uBAAmB;AAEvC,wBAAgB,aAAa,CAC3B,MAAM,EAAE,aAAa,GACpB,YAAY,GAAG,SAAS,GAAG,UAAU,GAAG,YAAY,CAetD;AAED,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,YAAY,EAAE,CAAA;AAC5D,YAAY,EACV,aAAa,EACb,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,GACnB,mBAAe;AAChB,YAAY,EACV,oBAAoB,EACpB,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,GACvB,MAAM,kBAAkB,CAAA"}
@@ -0,0 +1,54 @@
1
+ import type { NixxiePutOptions, NixxieSignedUrlOptions, NixxieStorageService, NixxieStoredFile } from '@nixxie-cms/core';
2
+ export type { NixxiePutOptions, NixxieSignedUrlOptions, NixxieStorageService, NixxieStoredFile };
3
+ export type LocalStorageConfig = {
4
+ driver: 'local';
5
+ /** Directory on disk where files are written. Default: '.nixxie-storage' */
6
+ baseDir?: string;
7
+ /** Public URL prefix that maps to `baseDir`, e.g. 'http://localhost:3000/files'. Default: '/files' */
8
+ baseUrl?: string;
9
+ };
10
+ export type S3StorageConfig = {
11
+ driver: 's3';
12
+ /** Target bucket name. */
13
+ bucket: string;
14
+ /** AWS region. */
15
+ region?: string;
16
+ /** Access key id — falls back to the AWS SDK default credential chain when omitted. */
17
+ accessKeyId?: string;
18
+ /** Secret access key. */
19
+ secretAccessKey?: string;
20
+ /** Custom endpoint (for S3-compatible services like R2, MinIO, Spaces). */
21
+ endpoint?: string;
22
+ /** Force path-style addressing (required by most S3-compatible services). */
23
+ forcePathStyle?: boolean;
24
+ /** Key prefix applied to every object. */
25
+ prefix?: string;
26
+ /** Public base URL used by `url()` when objects are public / served via CDN. */
27
+ publicBaseUrl?: string;
28
+ };
29
+ export type GcsStorageConfig = {
30
+ driver: 'gcs';
31
+ /** Target bucket name. */
32
+ bucket: string;
33
+ /** GCP project id. */
34
+ projectId?: string;
35
+ /** Path to a service-account key file. */
36
+ keyFilename?: string;
37
+ /** Key prefix applied to every object. */
38
+ prefix?: string;
39
+ /** Public base URL used by `url()`. */
40
+ publicBaseUrl?: string;
41
+ };
42
+ export type AzureStorageConfig = {
43
+ driver: 'azure';
44
+ /** Blob container name. */
45
+ container: string;
46
+ /** Full connection string for the storage account. */
47
+ connectionString: string;
48
+ /** Key prefix applied to every blob. */
49
+ prefix?: string;
50
+ /** Public base URL used by `url()`. */
51
+ publicBaseUrl?: string;
52
+ };
53
+ export type StorageConfig = LocalStorageConfig | S3StorageConfig | GcsStorageConfig | AzureStorageConfig;
54
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"../../../src","sources":["types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,EACpB,gBAAgB,EACjB,MAAM,kBAAkB,CAAA;AAEzB,YAAY,EAAE,gBAAgB,EAAE,sBAAsB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,CAAA;AAEhG,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,OAAO,CAAA;IACf,4EAA4E;IAC5E,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sGAAsG;IACtG,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,IAAI,CAAA;IACZ,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,kBAAkB;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,uFAAuF;IACvF,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,yBAAyB;IACzB,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6EAA6E;IAC7E,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,gFAAgF;IAChF,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,MAAM,EAAE,KAAK,CAAA;IACb,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,sBAAsB;IACtB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,0CAA0C;IAC1C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,uCAAuC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,MAAM,EAAE,OAAO,CAAA;IACf,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,sDAAsD;IACtD,gBAAgB,EAAE,MAAM,CAAA;IACxB,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,uCAAuC;IACvC,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,MAAM,MAAM,aAAa,GACrB,kBAAkB,GAClB,eAAe,GACf,gBAAgB,GAChB,kBAAkB,CAAA"}
@@ -0,0 +1,2 @@
1
+ export * from "./declarations/src/index.js";
2
+ //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibml4eGllLWNtcy1zdG9yYWdlLmNqcy5kLnRzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi9kZWNsYXJhdGlvbnMvc3JjL2luZGV4LmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEifQ==
@@ -0,0 +1,399 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var node_fs = require('node:fs');
6
+ var promises = require('node:fs/promises');
7
+ var node_path = require('node:path');
8
+
9
+ function loadAzure() {
10
+ try {
11
+ return require('@azure/storage-blob');
12
+ } catch {
13
+ throw new Error('@azure/storage-blob is required for the Azure driver. Run: npm install @azure/storage-blob');
14
+ }
15
+ }
16
+
17
+ /** Azure Blob Storage backend. */
18
+ class AzureStorage {
19
+ constructor(config) {
20
+ this.config = config;
21
+ this.prefix = config.prefix ? config.prefix.replace(/\/$/, '') + '/' : '';
22
+ const {
23
+ BlobServiceClient
24
+ } = loadAzure();
25
+ const service = BlobServiceClient.fromConnectionString(config.connectionString);
26
+ this.container = service.getContainerClient(config.container);
27
+ }
28
+ blob(key) {
29
+ return this.container.getBlockBlobClient(`${this.prefix}${key}`);
30
+ }
31
+ async put(key, data, options) {
32
+ const body = typeof data === 'string' ? Buffer.from(data) : Buffer.from(data);
33
+ await this.blob(key).uploadData(body, {
34
+ blobHTTPHeaders: options !== null && options !== void 0 && options.contentType ? {
35
+ blobContentType: options.contentType
36
+ } : undefined,
37
+ metadata: options === null || options === void 0 ? void 0 : options.metadata
38
+ });
39
+ return {
40
+ key,
41
+ url: this.url(key),
42
+ size: body.byteLength,
43
+ contentType: options === null || options === void 0 ? void 0 : options.contentType
44
+ };
45
+ }
46
+ async get(key) {
47
+ try {
48
+ return await this.blob(key).downloadToBuffer();
49
+ } catch (err) {
50
+ if ((err === null || err === void 0 ? void 0 : err.statusCode) === 404) return undefined;
51
+ throw err;
52
+ }
53
+ }
54
+ async delete(key) {
55
+ await this.blob(key).deleteIfExists();
56
+ }
57
+ async has(key) {
58
+ return Boolean(await this.blob(key).exists());
59
+ }
60
+ url(key) {
61
+ if (this.config.publicBaseUrl) return `${this.config.publicBaseUrl.replace(/\/$/, '')}/${this.prefix}${key}`;
62
+ return this.blob(key).url;
63
+ }
64
+ async signedUrl(key, options) {
65
+ var _credential, _options$expiresIn;
66
+ const {
67
+ BlobSASPermissions,
68
+ generateBlobSASQueryParameters
69
+ } = loadAzure();
70
+ const blob = this.blob(key);
71
+ const credential = (_credential = this.container.credential) !== null && _credential !== void 0 ? _credential : blob.credential;
72
+ const expiresOn = new Date(Date.now() + ((_options$expiresIn = options === null || options === void 0 ? void 0 : options.expiresIn) !== null && _options$expiresIn !== void 0 ? _options$expiresIn : 900) * 1000);
73
+ const permissions = BlobSASPermissions.parse((options === null || options === void 0 ? void 0 : options.operation) === 'put' ? 'cw' : 'r');
74
+ try {
75
+ const sas = generateBlobSASQueryParameters({
76
+ containerName: this.config.container,
77
+ blobName: `${this.prefix}${key}`,
78
+ permissions,
79
+ expiresOn
80
+ }, credential).toString();
81
+ return `${blob.url}?${sas}`;
82
+ } catch {
83
+ // Falls back to the plain URL when SAS generation is unavailable (e.g. token credential).
84
+ return blob.url;
85
+ }
86
+ }
87
+ async list(prefix = '') {
88
+ const out = [];
89
+ for await (const item of this.container.listBlobsFlat({
90
+ prefix: `${this.prefix}${prefix}`
91
+ })) {
92
+ const name = item.name;
93
+ out.push(this.prefix && name.startsWith(this.prefix) ? name.slice(this.prefix.length) : name);
94
+ }
95
+ return out;
96
+ }
97
+ }
98
+
99
+ function loadGcs() {
100
+ try {
101
+ return require('@google-cloud/storage');
102
+ } catch {
103
+ throw new Error('@google-cloud/storage is required for the GCS driver. Run: npm install @google-cloud/storage');
104
+ }
105
+ }
106
+
107
+ /** Google Cloud Storage backend. */
108
+ class GcsStorage {
109
+ constructor(config) {
110
+ this.config = config;
111
+ this.prefix = config.prefix ? config.prefix.replace(/\/$/, '') + '/' : '';
112
+ const {
113
+ Storage
114
+ } = loadGcs();
115
+ const client = new Storage({
116
+ projectId: config.projectId,
117
+ keyFilename: config.keyFilename
118
+ });
119
+ this.bucket = client.bucket(config.bucket);
120
+ }
121
+ file(key) {
122
+ return this.bucket.file(`${this.prefix}${key}`);
123
+ }
124
+ async put(key, data, options) {
125
+ const body = typeof data === 'string' ? Buffer.from(data) : Buffer.from(data);
126
+ const file = this.file(key);
127
+ await file.save(body, {
128
+ contentType: options === null || options === void 0 ? void 0 : options.contentType,
129
+ metadata: options !== null && options !== void 0 && options.metadata ? {
130
+ metadata: options.metadata
131
+ } : undefined
132
+ });
133
+ if (options !== null && options !== void 0 && options.public) await file.makePublic();
134
+ return {
135
+ key,
136
+ url: this.url(key),
137
+ size: body.byteLength,
138
+ contentType: options === null || options === void 0 ? void 0 : options.contentType
139
+ };
140
+ }
141
+ async get(key) {
142
+ try {
143
+ const [contents] = await this.file(key).download();
144
+ return contents;
145
+ } catch (err) {
146
+ if ((err === null || err === void 0 ? void 0 : err.code) === 404) return undefined;
147
+ throw err;
148
+ }
149
+ }
150
+ async delete(key) {
151
+ await this.file(key).delete({
152
+ ignoreNotFound: true
153
+ });
154
+ }
155
+ async has(key) {
156
+ const [exists] = await this.file(key).exists();
157
+ return Boolean(exists);
158
+ }
159
+ url(key) {
160
+ if (this.config.publicBaseUrl) return `${this.config.publicBaseUrl.replace(/\/$/, '')}/${this.prefix}${key}`;
161
+ return `https://storage.googleapis.com/${this.config.bucket}/${this.prefix}${key}`;
162
+ }
163
+ async signedUrl(key, options) {
164
+ var _options$expiresIn;
165
+ const [url] = await this.file(key).getSignedUrl({
166
+ action: (options === null || options === void 0 ? void 0 : options.operation) === 'put' ? 'write' : 'read',
167
+ expires: Date.now() + ((_options$expiresIn = options === null || options === void 0 ? void 0 : options.expiresIn) !== null && _options$expiresIn !== void 0 ? _options$expiresIn : 900) * 1000,
168
+ contentType: (options === null || options === void 0 ? void 0 : options.operation) === 'put' ? options === null || options === void 0 ? void 0 : options.contentType : undefined
169
+ });
170
+ return url;
171
+ }
172
+ async list(prefix = '') {
173
+ const [files] = await this.bucket.getFiles({
174
+ prefix: `${this.prefix}${prefix}`
175
+ });
176
+ return files.map(f => f.name).map(k => this.prefix && k.startsWith(this.prefix) ? k.slice(this.prefix.length) : k);
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Filesystem-backed storage. Good for development and single-node deployments.
182
+ * `signedUrl()` falls back to the plain public URL since the local disk cannot sign.
183
+ */
184
+ class LocalStorage {
185
+ constructor(config) {
186
+ var _config$baseDir, _config$baseUrl;
187
+ this.baseDir = (_config$baseDir = config.baseDir) !== null && _config$baseDir !== void 0 ? _config$baseDir : '.nixxie-storage';
188
+ this.baseUrl = ((_config$baseUrl = config.baseUrl) !== null && _config$baseUrl !== void 0 ? _config$baseUrl : '/files').replace(/\/$/, '');
189
+ }
190
+ path(key) {
191
+ // Confine resolved paths to baseDir so keys like "../../etc/passwd" (or absolute paths)
192
+ // cannot escape the storage root.
193
+ const root = node_path.resolve(this.baseDir);
194
+ const full = node_path.resolve(root, key);
195
+ if (full !== root && !full.startsWith(root + node_path.sep)) {
196
+ throw new Error(`Invalid storage key (path traversal detected): ${key}`);
197
+ }
198
+ return full;
199
+ }
200
+ async put(key, data, options) {
201
+ const filePath = this.path(key);
202
+ await promises.mkdir(node_path.dirname(filePath), {
203
+ recursive: true
204
+ });
205
+ const buffer = typeof data === 'string' ? Buffer.from(data) : Buffer.from(data);
206
+ await promises.writeFile(filePath, buffer);
207
+ return {
208
+ key,
209
+ url: this.url(key),
210
+ size: buffer.byteLength,
211
+ contentType: options === null || options === void 0 ? void 0 : options.contentType
212
+ };
213
+ }
214
+ async get(key) {
215
+ const filePath = this.path(key);
216
+ if (!node_fs.existsSync(filePath)) return undefined;
217
+ return promises.readFile(filePath);
218
+ }
219
+ async delete(key) {
220
+ await promises.rm(this.path(key), {
221
+ force: true
222
+ });
223
+ }
224
+ async has(key) {
225
+ try {
226
+ await promises.stat(this.path(key));
227
+ return true;
228
+ } catch {
229
+ return false;
230
+ }
231
+ }
232
+ url(key) {
233
+ return `${this.baseUrl}/${key.split(node_path.sep).join('/')}`;
234
+ }
235
+ async signedUrl(key, _options) {
236
+ return this.url(key);
237
+ }
238
+ async list(prefix = '') {
239
+ const root = this.baseDir;
240
+ const out = [];
241
+ const walk = async dir => {
242
+ let entries;
243
+ try {
244
+ entries = await promises.readdir(dir, {
245
+ withFileTypes: true
246
+ });
247
+ } catch {
248
+ return;
249
+ }
250
+ for (const entry of entries) {
251
+ const full = node_path.join(dir, entry.name);
252
+ if (entry.isDirectory()) await walk(full);else out.push(node_path.relative(root, full).split(node_path.sep).join(node_path.posix.sep));
253
+ }
254
+ };
255
+ await walk(root);
256
+ return out.filter(k => k.startsWith(prefix));
257
+ }
258
+ }
259
+
260
+ function loadS3() {
261
+ try {
262
+ return {
263
+ s3: require('@aws-sdk/client-s3'),
264
+ presigner: require('@aws-sdk/s3-request-presigner')
265
+ };
266
+ } catch {
267
+ throw new Error('@aws-sdk/client-s3 and @aws-sdk/s3-request-presigner are required for the S3 driver. Run: npm install @aws-sdk/client-s3 @aws-sdk/s3-request-presigner');
268
+ }
269
+ }
270
+
271
+ /** AWS S3 (and S3-compatible: R2, MinIO, Spaces) storage backend. */
272
+ class S3Storage {
273
+ constructor(config) {
274
+ this.config = config;
275
+ this.prefix = config.prefix ? config.prefix.replace(/\/$/, '') + '/' : '';
276
+ const {
277
+ s3,
278
+ presigner
279
+ } = loadS3();
280
+ this.s3 = s3;
281
+ this.presigner = presigner;
282
+ this.client = new s3.S3Client({
283
+ region: config.region,
284
+ endpoint: config.endpoint,
285
+ forcePathStyle: config.forcePathStyle,
286
+ credentials: config.accessKeyId && config.secretAccessKey ? {
287
+ accessKeyId: config.accessKeyId,
288
+ secretAccessKey: config.secretAccessKey
289
+ } : undefined
290
+ });
291
+ }
292
+ k(key) {
293
+ return `${this.prefix}${key}`;
294
+ }
295
+ async put(key, data, options) {
296
+ const body = typeof data === 'string' ? Buffer.from(data) : Buffer.from(data);
297
+ await this.client.send(new this.s3.PutObjectCommand({
298
+ Bucket: this.config.bucket,
299
+ Key: this.k(key),
300
+ Body: body,
301
+ ContentType: options === null || options === void 0 ? void 0 : options.contentType,
302
+ ACL: options !== null && options !== void 0 && options.public ? 'public-read' : undefined,
303
+ Metadata: options === null || options === void 0 ? void 0 : options.metadata
304
+ }));
305
+ return {
306
+ key,
307
+ url: this.url(key),
308
+ size: body.byteLength,
309
+ contentType: options === null || options === void 0 ? void 0 : options.contentType
310
+ };
311
+ }
312
+ async get(key) {
313
+ try {
314
+ var _res$Body;
315
+ const res = await this.client.send(new this.s3.GetObjectCommand({
316
+ Bucket: this.config.bucket,
317
+ Key: this.k(key)
318
+ }));
319
+ const bytes = await ((_res$Body = res.Body) === null || _res$Body === void 0 ? void 0 : _res$Body.transformToByteArray());
320
+ return bytes ? Buffer.from(bytes) : undefined;
321
+ } catch (err) {
322
+ var _err$$metadata;
323
+ if ((err === null || err === void 0 ? void 0 : err.name) === 'NoSuchKey' || (err === null || err === void 0 || (_err$$metadata = err.$metadata) === null || _err$$metadata === void 0 ? void 0 : _err$$metadata.httpStatusCode) === 404) return undefined;
324
+ throw err;
325
+ }
326
+ }
327
+ async delete(key) {
328
+ await this.client.send(new this.s3.DeleteObjectCommand({
329
+ Bucket: this.config.bucket,
330
+ Key: this.k(key)
331
+ }));
332
+ }
333
+ async has(key) {
334
+ try {
335
+ await this.client.send(new this.s3.HeadObjectCommand({
336
+ Bucket: this.config.bucket,
337
+ Key: this.k(key)
338
+ }));
339
+ return true;
340
+ } catch {
341
+ return false;
342
+ }
343
+ }
344
+ url(key) {
345
+ var _this$config$region;
346
+ if (this.config.publicBaseUrl) return `${this.config.publicBaseUrl.replace(/\/$/, '')}/${this.k(key)}`;
347
+ const host = this.config.endpoint ? `${this.config.endpoint.replace(/\/$/, '')}/${this.config.bucket}` : `https://${this.config.bucket}.s3.${(_this$config$region = this.config.region) !== null && _this$config$region !== void 0 ? _this$config$region : 'us-east-1'}.amazonaws.com`;
348
+ return `${host}/${this.k(key)}`;
349
+ }
350
+ async signedUrl(key, options) {
351
+ var _options$expiresIn;
352
+ const command = (options === null || options === void 0 ? void 0 : options.operation) === 'put' ? new this.s3.PutObjectCommand({
353
+ Bucket: this.config.bucket,
354
+ Key: this.k(key),
355
+ ContentType: options.contentType
356
+ }) : new this.s3.GetObjectCommand({
357
+ Bucket: this.config.bucket,
358
+ Key: this.k(key)
359
+ });
360
+ return this.presigner.getSignedUrl(this.client, command, {
361
+ expiresIn: (_options$expiresIn = options === null || options === void 0 ? void 0 : options.expiresIn) !== null && _options$expiresIn !== void 0 ? _options$expiresIn : 900
362
+ });
363
+ }
364
+ async list(prefix = '') {
365
+ var _res$Contents;
366
+ const res = await this.client.send(new this.s3.ListObjectsV2Command({
367
+ Bucket: this.config.bucket,
368
+ Prefix: this.k(prefix)
369
+ }));
370
+ return ((_res$Contents = res.Contents) !== null && _res$Contents !== void 0 ? _res$Contents : []).map(o => {
371
+ var _o$Key;
372
+ return (_o$Key = o.Key) !== null && _o$Key !== void 0 ? _o$Key : '';
373
+ }).map(k => this.prefix && k.startsWith(this.prefix) ? k.slice(this.prefix.length) : k).filter(Boolean);
374
+ }
375
+ }
376
+
377
+ function createStorage(config) {
378
+ switch (config.driver) {
379
+ case 'local':
380
+ return new LocalStorage(config);
381
+ case 's3':
382
+ return new S3Storage(config);
383
+ case 'gcs':
384
+ return new GcsStorage(config);
385
+ case 'azure':
386
+ return new AzureStorage(config);
387
+ default:
388
+ {
389
+ const exhaustive = config;
390
+ throw new Error(`Unknown storage driver: ${exhaustive.driver}`);
391
+ }
392
+ }
393
+ }
394
+
395
+ exports.AzureStorage = AzureStorage;
396
+ exports.GcsStorage = GcsStorage;
397
+ exports.LocalStorage = LocalStorage;
398
+ exports.S3Storage = S3Storage;
399
+ exports.createStorage = createStorage;