@storyshelf/storage-azure 0.5.3
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 +90 -0
- package/dist/client.mjs +32 -0
- package/dist/client.mjs.map +1 -0
- package/dist/errors.mjs +41 -0
- package/dist/errors.mjs.map +1 -0
- package/dist/index.d.mts +16 -0
- package/dist/index.mjs +58 -0
- package/dist/index.mjs.map +1 -0
- package/dist/keys.d.mts +9 -0
- package/dist/keys.mjs +17 -0
- package/dist/keys.mjs.map +1 -0
- package/dist/lifecycle.mjs +36 -0
- package/dist/lifecycle.mjs.map +1 -0
- package/dist/operations.mjs +54 -0
- package/dist/operations.mjs.map +1 -0
- package/dist/streams.mjs +46 -0
- package/dist/streams.mjs.map +1 -0
- package/dist/types.d.mts +26 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Siddhant Gupta
|
|
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
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# @storyshelf/storage-azure
|
|
2
|
+
|
|
3
|
+
Azure Blob Storage adapter for StoryShelf: reads and writes blobs to an Azure Storage container, including Azurite emulator support.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
nub add @storyshelf/storage-azure
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
or
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
npm install @storyshelf/storage-azure
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { createAzureStorage } from "@storyshelf/storage-azure";
|
|
21
|
+
import { createShelfApp } from "@storyshelf/app";
|
|
22
|
+
|
|
23
|
+
// Via connection string (recommended for local / Azurite)
|
|
24
|
+
const storage = createAzureStorage({
|
|
25
|
+
container: "storyshelf",
|
|
26
|
+
prefix: "storyshelf", // optional
|
|
27
|
+
connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
// Via account name + key
|
|
31
|
+
const storage2 = createAzureStorage({
|
|
32
|
+
container: "storyshelf",
|
|
33
|
+
accountName: "myaccount",
|
|
34
|
+
accountKey: process.env.AZURE_STORAGE_KEY,
|
|
35
|
+
accountUrl: "https://myaccount.blob.core.windows.net", // optional, derived from accountName if omitted
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const app = createShelfApp({ database, storage });
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Inject a pre-configured client (tests / custom credential rotation):
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { BlobServiceClient } from "@azure/storage-blob";
|
|
45
|
+
|
|
46
|
+
const service = BlobServiceClient.fromConnectionString(connStr);
|
|
47
|
+
const containerClient = service.getContainerClient("storyshelf");
|
|
48
|
+
const storage = createAzureStorage({ container: "storyshelf", client: containerClient });
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Azurite (local emulator):
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
const storage = createAzureStorage({
|
|
55
|
+
container: "storyshelf",
|
|
56
|
+
connectionString:
|
|
57
|
+
"DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;",
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## API
|
|
62
|
+
|
|
63
|
+
### `AzureStorageOptions`
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
interface AzureStorageOptions {
|
|
67
|
+
container: string; // required container name
|
|
68
|
+
prefix?: string; // optional blob prefix, defaults to ""
|
|
69
|
+
client?: ContainerClient; // optional pre-configured container client
|
|
70
|
+
connectionString?: string; // optional Azure connection string
|
|
71
|
+
accountName?: string; // optional storage account name
|
|
72
|
+
accountKey?: string; // optional storage account key
|
|
73
|
+
accountUrl?: string; // optional blob service URL (e.g. https://myaccount.blob.core.windows.net)
|
|
74
|
+
sasToken?: string; // optional SAS token (without leading ? OK)
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### `createAzureStorage(options: AzureStorageOptions): StorageAdapter`
|
|
79
|
+
|
|
80
|
+
Creates a `StorageAdapter` backed by the configured Azure container. The returned adapter implements every method of the `StorageAdapter` interface (`read`, `write`, `delete`, `exists`, `list(prefix)`, `readStream`, `writeStream`).
|
|
81
|
+
|
|
82
|
+
### `azureKey(prefix: string, path: string): string`
|
|
83
|
+
|
|
84
|
+
Helper that joins a storage prefix with a relative path into a blob name.
|
|
85
|
+
|
|
86
|
+
## How it fits in
|
|
87
|
+
|
|
88
|
+
`storage-azure` is the `storage` option for `createShelfApp` in Azure deployments. It implements the same `StorageAdapter` interface as `@storyshelf/storage-local`, `@storyshelf/storage-s3`, and `@storyshelf/storage-gcs`, so switching between local disk, S3, GCS, and Azure requires no changes elsewhere.
|
|
89
|
+
|
|
90
|
+
See `docs/architecture.md` and ADR 0006.
|
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { BlobServiceClient, StorageSharedKeyCredential } from "@azure/storage-blob";
|
|
5
|
+
//#region src/client.ts
|
|
6
|
+
/** Resolve the container client for the configured credentials. */
|
|
7
|
+
function resolveContainerClient(options, containerName) {
|
|
8
|
+
if (options.client) return options.client;
|
|
9
|
+
if (options.connectionString) return BlobServiceClient.fromConnectionString(options.connectionString).getContainerClient(containerName);
|
|
10
|
+
return resolveFromAccount(options, containerName);
|
|
11
|
+
}
|
|
12
|
+
function resolveFromAccount(options, containerName) {
|
|
13
|
+
const url = options.accountUrl ?? buildAccountUrl(options.accountName);
|
|
14
|
+
if (!url) throw new Error("createAzureStorage: provide `client`, `connectionString`, or `accountName`/`accountUrl`");
|
|
15
|
+
if (options.accountName && options.accountKey) {
|
|
16
|
+
const cred = new StorageSharedKeyCredential(options.accountName, options.accountKey);
|
|
17
|
+
return new BlobServiceClient(url, cred).getContainerClient(containerName);
|
|
18
|
+
}
|
|
19
|
+
if (options.sasToken) {
|
|
20
|
+
const token = options.sasToken.replace(/^\?/u, "");
|
|
21
|
+
return new BlobServiceClient(`${url}?${token}`).getContainerClient(containerName);
|
|
22
|
+
}
|
|
23
|
+
return new BlobServiceClient(url).getContainerClient(containerName);
|
|
24
|
+
}
|
|
25
|
+
function buildAccountUrl(accountName) {
|
|
26
|
+
if (!accountName) return;
|
|
27
|
+
return `https://${accountName}.blob.core.windows.net`;
|
|
28
|
+
}
|
|
29
|
+
//#endregion
|
|
30
|
+
export { resolveContainerClient };
|
|
31
|
+
|
|
32
|
+
//# sourceMappingURL=client.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.mjs","names":[],"sources":["../src/client.ts"],"sourcesContent":["import {\n BlobServiceClient,\n StorageSharedKeyCredential,\n type ContainerClient,\n} from \"@azure/storage-blob\";\nimport type { AzureStorageOptions } from \"./types.ts\";\n\n/** Resolve the container client for the configured credentials. */\nexport function resolveContainerClient(\n options: AzureStorageOptions,\n containerName: string,\n): ContainerClient {\n if (options.client) {\n return options.client;\n }\n if (options.connectionString) {\n return BlobServiceClient.fromConnectionString(options.connectionString).getContainerClient(\n containerName,\n );\n }\n return resolveFromAccount(options, containerName);\n}\n\n/** Shared Azure container context threaded through operations. */\nexport interface AzureContext {\n container: ContainerClient;\n prefix: string;\n}\n\nfunction resolveFromAccount(options: AzureStorageOptions, containerName: string): ContainerClient {\n const url = options.accountUrl ?? buildAccountUrl(options.accountName);\n if (!url) {\n throw new Error(\n \"createAzureStorage: provide `client`, `connectionString`, or `accountName`/`accountUrl`\",\n );\n }\n if (options.accountName && options.accountKey) {\n const cred = new StorageSharedKeyCredential(options.accountName, options.accountKey);\n return new BlobServiceClient(url, cred).getContainerClient(containerName);\n }\n if (options.sasToken) {\n const token = options.sasToken.replace(/^\\?/u, \"\");\n return new BlobServiceClient(`${url}?${token}`).getContainerClient(containerName);\n }\n return new BlobServiceClient(url).getContainerClient(containerName);\n}\n\nfunction buildAccountUrl(accountName: string | undefined): string | undefined {\n if (!accountName) {\n return undefined;\n }\n return `https://${accountName}.blob.core.windows.net`;\n}\n"],"mappings":";;;;;;AAQA,SAAgB,uBACd,SACA,eACiB;CACjB,IAAI,QAAQ,QACV,OAAO,QAAQ;CAEjB,IAAI,QAAQ,kBACV,OAAO,kBAAkB,qBAAqB,QAAQ,gBAAgB,CAAC,CAAC,mBACtE,aACF;CAEF,OAAO,mBAAmB,SAAS,aAAa;AAClD;AAQA,SAAS,mBAAmB,SAA8B,eAAwC;CAChG,MAAM,MAAM,QAAQ,cAAc,gBAAgB,QAAQ,WAAW;CACrE,IAAI,CAAC,KACH,MAAM,IAAI,MACR,yFACF;CAEF,IAAI,QAAQ,eAAe,QAAQ,YAAY;EAC7C,MAAM,OAAO,IAAI,2BAA2B,QAAQ,aAAa,QAAQ,UAAU;EACnF,OAAO,IAAI,kBAAkB,KAAK,IAAI,CAAC,CAAC,mBAAmB,aAAa;CAC1E;CACA,IAAI,QAAQ,UAAU;EACpB,MAAM,QAAQ,QAAQ,SAAS,QAAQ,QAAQ,EAAE;EACjD,OAAO,IAAI,kBAAkB,GAAG,IAAI,GAAG,OAAO,CAAC,CAAC,mBAAmB,aAAa;CAClF;CACA,OAAO,IAAI,kBAAkB,GAAG,CAAC,CAAC,mBAAmB,aAAa;AACpE;AAEA,SAAS,gBAAgB,aAAqD;CAC5E,IAAI,CAAC,aACH;CAEF,OAAO,WAAW,YAAY;AAChC"}
|
package/dist/errors.mjs
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import "node:stream";
|
|
5
|
+
//#region src/errors.ts
|
|
6
|
+
/** Not-found guards and stream/collect helpers for Azure blob I/O. */
|
|
7
|
+
/** True when an Azure SDK error means the blob (or container) is missing. */
|
|
8
|
+
function isNotFound(error) {
|
|
9
|
+
const err = error;
|
|
10
|
+
if (err.statusCode === 404) return true;
|
|
11
|
+
return err.code === "BlobNotFound" || err.code === "ContainerNotFound" || err.code === "404";
|
|
12
|
+
}
|
|
13
|
+
/** Download a blob through the `download()` response stream. */
|
|
14
|
+
async function collectDownload(blob) {
|
|
15
|
+
const body = (await blob.download()).readableStreamBody;
|
|
16
|
+
if (!body) return Buffer.alloc(0);
|
|
17
|
+
const chunks = [];
|
|
18
|
+
for await (const chunk of body) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk));
|
|
19
|
+
return Buffer.concat(chunks);
|
|
20
|
+
}
|
|
21
|
+
/** Buffer an incoming readable into a single Buffer. */
|
|
22
|
+
async function collectStream(stream) {
|
|
23
|
+
const chunks = [];
|
|
24
|
+
for await (const chunk of stream) chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk));
|
|
25
|
+
return Buffer.concat(chunks);
|
|
26
|
+
}
|
|
27
|
+
/** Best-effort blob delete used to clean up after failed stream uploads. */
|
|
28
|
+
async function deleteBlobQuiet(blob) {
|
|
29
|
+
try {
|
|
30
|
+
const withDelete = blob;
|
|
31
|
+
if (typeof withDelete.deleteIfExists === "function") {
|
|
32
|
+
await withDelete.deleteIfExists();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
await withDelete.delete?.();
|
|
36
|
+
} catch {}
|
|
37
|
+
}
|
|
38
|
+
//#endregion
|
|
39
|
+
export { collectDownload, collectStream, deleteBlobQuiet, isNotFound };
|
|
40
|
+
|
|
41
|
+
//# sourceMappingURL=errors.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.mjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["import type { ContainerClient } from \"@azure/storage-blob\";\nimport { Readable } from \"node:stream\";\n\n/** Not-found guards and stream/collect helpers for Azure blob I/O. */\n\n/** True when an Azure SDK error means the blob (or container) is missing. */\nexport function isNotFound(error: unknown): boolean {\n const err = error as { statusCode?: number; code?: string };\n if (err.statusCode === 404) {\n return true;\n }\n return err.code === \"BlobNotFound\" || err.code === \"ContainerNotFound\" || err.code === \"404\";\n}\n\n/** Download a blob through the `download()` response stream. */\nexport async function collectDownload(\n blob: ReturnType<ContainerClient[\"getBlockBlobClient\"]>,\n): Promise<Buffer> {\n const response = await blob.download();\n const body = response.readableStreamBody as unknown as Readable | undefined;\n if (!body) {\n return Buffer.alloc(0);\n }\n const chunks: Buffer[] = [];\n for await (const chunk of body) {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : Buffer.from(chunk as Uint8Array));\n }\n return Buffer.concat(chunks);\n}\n\n/** Buffer an incoming readable into a single Buffer. */\nexport async function collectStream(stream: Readable): Promise<Buffer> {\n const chunks: Buffer[] = [];\n for await (const chunk of stream) {\n chunks.push(typeof chunk === \"string\" ? Buffer.from(chunk) : Buffer.from(chunk as Uint8Array));\n }\n return Buffer.concat(chunks);\n}\n\n/** Best-effort blob delete used to clean up after failed stream uploads. */\nexport async function deleteBlobQuiet(\n blob: ReturnType<ContainerClient[\"getBlockBlobClient\"]>,\n): Promise<void> {\n try {\n const withDelete = blob as unknown as {\n deleteIfExists?: () => Promise<void>;\n delete?: () => Promise<void>;\n };\n if (typeof withDelete.deleteIfExists === \"function\") {\n await withDelete.deleteIfExists();\n return;\n }\n await withDelete.delete?.();\n } catch {\n // ignore - best-effort cleanup\n }\n}\n"],"mappings":";;;;;;;AAMA,SAAgB,WAAW,OAAyB;CAClD,MAAM,MAAM;CACZ,IAAI,IAAI,eAAe,KACrB,OAAO;CAET,OAAO,IAAI,SAAS,kBAAkB,IAAI,SAAS,uBAAuB,IAAI,SAAS;AACzF;;AAGA,eAAsB,gBACpB,MACiB;CAEjB,MAAM,QAAO,MADU,KAAK,SAAS,EAAA,CACf;CACtB,IAAI,CAAC,MACH,OAAO,OAAO,MAAM,CAAC;CAEvB,MAAM,SAAmB,CAAC;CAC1B,WAAW,MAAM,SAAS,MACxB,OAAO,KAAK,OAAO,UAAU,WAAW,OAAO,KAAK,KAAK,IAAI,OAAO,KAAK,KAAmB,CAAC;CAE/F,OAAO,OAAO,OAAO,MAAM;AAC7B;;AAGA,eAAsB,cAAc,QAAmC;CACrE,MAAM,SAAmB,CAAC;CAC1B,WAAW,MAAM,SAAS,QACxB,OAAO,KAAK,OAAO,UAAU,WAAW,OAAO,KAAK,KAAK,IAAI,OAAO,KAAK,KAAmB,CAAC;CAE/F,OAAO,OAAO,OAAO,MAAM;AAC7B;;AAGA,eAAsB,gBACpB,MACe;CACf,IAAI;EACF,MAAM,aAAa;EAInB,IAAI,OAAO,WAAW,mBAAmB,YAAY;GACnD,MAAM,WAAW,eAAe;GAChC;EACF;EACA,MAAM,WAAW,SAAS;CAC5B,QAAQ,CAER;AACF"}
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { AzureStorageOptions } from "./types.mjs";
|
|
4
|
+
import { azureKey } from "./keys.mjs";
|
|
5
|
+
import { StorageAdapter } from "@storyshelf/core/adapter/storage";
|
|
6
|
+
//#region src/index.d.ts
|
|
7
|
+
/**
|
|
8
|
+
* Create an Azure Blob Storage-backed StorageAdapter.
|
|
9
|
+
*
|
|
10
|
+
* @param options - Azure configuration options.
|
|
11
|
+
* @returns A StorageAdapter backed by the configured Azure container.
|
|
12
|
+
*/
|
|
13
|
+
declare function createAzureStorage(options: AzureStorageOptions): StorageAdapter;
|
|
14
|
+
//#endregion
|
|
15
|
+
export { type AzureStorageOptions, azureKey, createAzureStorage };
|
|
16
|
+
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { resolveContainerClient } from "./client.mjs";
|
|
5
|
+
import { buildAzureLifecycle } from "./lifecycle.mjs";
|
|
6
|
+
import { azureKey } from "./keys.mjs";
|
|
7
|
+
import { azureDelete, azureExists, azureList, azureRead, azureWrite } from "./operations.mjs";
|
|
8
|
+
import { azureReadStream, azureWriteStream } from "./streams.mjs";
|
|
9
|
+
//#region src/index.ts
|
|
10
|
+
/**
|
|
11
|
+
* Create an Azure Blob Storage-backed StorageAdapter.
|
|
12
|
+
*
|
|
13
|
+
* @param options - Azure configuration options.
|
|
14
|
+
* @returns A StorageAdapter backed by the configured Azure container.
|
|
15
|
+
*/
|
|
16
|
+
function createAzureStorage(options) {
|
|
17
|
+
const { container: containerName, prefix = "" } = options;
|
|
18
|
+
const container = resolveContainerClient(options, containerName);
|
|
19
|
+
const ctx = {
|
|
20
|
+
container,
|
|
21
|
+
prefix
|
|
22
|
+
};
|
|
23
|
+
return {
|
|
24
|
+
metadata: {
|
|
25
|
+
name: "Azure Blob Storage",
|
|
26
|
+
version: globalThis.__PKG_VERSION__ ?? "0.0.0",
|
|
27
|
+
description: "Azure Blob Storage adapter",
|
|
28
|
+
kind: "azure",
|
|
29
|
+
category: "storage"
|
|
30
|
+
},
|
|
31
|
+
lifecycle: buildAzureLifecycle(container, containerName),
|
|
32
|
+
async read(path) {
|
|
33
|
+
return await azureRead(ctx, path);
|
|
34
|
+
},
|
|
35
|
+
async write(path, data) {
|
|
36
|
+
await azureWrite(ctx, path, data);
|
|
37
|
+
},
|
|
38
|
+
async delete(path) {
|
|
39
|
+
await azureDelete(ctx, path);
|
|
40
|
+
},
|
|
41
|
+
async exists(path) {
|
|
42
|
+
return await azureExists(ctx, path);
|
|
43
|
+
},
|
|
44
|
+
async list(listPrefix) {
|
|
45
|
+
return await azureList(ctx, listPrefix);
|
|
46
|
+
},
|
|
47
|
+
async writeStream(path, stream) {
|
|
48
|
+
await azureWriteStream(ctx, path, stream);
|
|
49
|
+
},
|
|
50
|
+
async readStream(path) {
|
|
51
|
+
return await azureReadStream(ctx, path);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
//#endregion
|
|
56
|
+
export { azureKey, createAzureStorage };
|
|
57
|
+
|
|
58
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * Azure Blob Storage adapter for StoryShelf.\n *\n * Persists captured screenshots and diff overlays in an Azure Blob container.\n * The CLI uploads the built Storybook; the server renders stories with\n * Playwright and stores the resulting blobs here.\n */\nimport type { StorageAdapter } from \"@storyshelf/core/adapter/storage\";\nimport { resolveContainerClient, type AzureContext } from \"./client.ts\";\nimport { buildAzureLifecycle } from \"./lifecycle.ts\";\nimport { azureDelete, azureExists, azureList, azureRead, azureWrite } from \"./operations.ts\";\nimport { azureReadStream, azureWriteStream } from \"./streams.ts\";\nimport type { AzureStorageOptions } from \"./types.ts\";\n\ndeclare const __PKG_VERSION__: string | undefined;\n\n/**\n * Create an Azure Blob Storage-backed StorageAdapter.\n *\n * @param options - Azure configuration options.\n * @returns A StorageAdapter backed by the configured Azure container.\n */\nexport function createAzureStorage(options: AzureStorageOptions): StorageAdapter {\n const { container: containerName, prefix = \"\" } = options;\n const container = resolveContainerClient(options, containerName);\n const ctx: AzureContext = { container, prefix };\n\n return {\n metadata: {\n name: \"Azure Blob Storage\",\n version: (globalThis as unknown as { __PKG_VERSION__?: string }).__PKG_VERSION__ ?? \"0.0.0\",\n description: \"Azure Blob Storage adapter\",\n kind: \"azure\",\n category: \"storage\",\n },\n lifecycle: buildAzureLifecycle(container, containerName),\n async read(path) {\n return await azureRead(ctx, path);\n },\n async write(path, data) {\n await azureWrite(ctx, path, data);\n },\n async delete(path) {\n await azureDelete(ctx, path);\n },\n async exists(path) {\n return await azureExists(ctx, path);\n },\n async list(listPrefix) {\n return await azureList(ctx, listPrefix);\n },\n async writeStream(path, stream) {\n await azureWriteStream(ctx, path, stream);\n },\n async readStream(path) {\n return await azureReadStream(ctx, path);\n },\n };\n}\n\nexport type { AzureStorageOptions } from \"./types.ts\";\nexport { azureKey } from \"./keys.ts\";\n"],"mappings":";;;;;;;;;;;;;;;AAsBA,SAAgB,mBAAmB,SAA8C;CAC/E,MAAM,EAAE,WAAW,eAAe,SAAS,OAAO;CAClD,MAAM,YAAY,uBAAuB,SAAS,aAAa;CAC/D,MAAM,MAAoB;EAAE;EAAW;CAAO;CAE9C,OAAO;EACL,UAAU;GACR,MAAM;GACN,SAAU,WAAuD,mBAAmB;GACpF,aAAa;GACb,MAAM;GACN,UAAU;EACZ;EACA,WAAW,oBAAoB,WAAW,aAAa;EACvD,MAAM,KAAK,MAAM;GACf,OAAO,MAAM,UAAU,KAAK,IAAI;EAClC;EACA,MAAM,MAAM,MAAM,MAAM;GACtB,MAAM,WAAW,KAAK,MAAM,IAAI;EAClC;EACA,MAAM,OAAO,MAAM;GACjB,MAAM,YAAY,KAAK,IAAI;EAC7B;EACA,MAAM,OAAO,MAAM;GACjB,OAAO,MAAM,YAAY,KAAK,IAAI;EACpC;EACA,MAAM,KAAK,YAAY;GACrB,OAAO,MAAM,UAAU,KAAK,UAAU;EACxC;EACA,MAAM,YAAY,MAAM,QAAQ;GAC9B,MAAM,iBAAiB,KAAK,MAAM,MAAM;EAC1C;EACA,MAAM,WAAW,MAAM;GACrB,OAAO,MAAM,gBAAgB,KAAK,IAAI;EACxC;CACF;AACF"}
|
package/dist/keys.d.mts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
//#region src/keys.d.ts
|
|
4
|
+
/** Key join/split for Azure blob names. */
|
|
5
|
+
/** Join an optional prefix with a storage path. */
|
|
6
|
+
declare function azureKey(prefix: string, path: string): string;
|
|
7
|
+
//#endregion
|
|
8
|
+
export { azureKey };
|
|
9
|
+
//# sourceMappingURL=keys.d.mts.map
|
package/dist/keys.mjs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
//#region src/keys.ts
|
|
5
|
+
/** Key join/split for Azure blob names. */
|
|
6
|
+
/** Join an optional prefix with a storage path. */
|
|
7
|
+
function azureKey(prefix, path) {
|
|
8
|
+
return prefix === "" ? path : `${prefix}/${path}`;
|
|
9
|
+
}
|
|
10
|
+
/** Strip a storage prefix back off a blob name. */
|
|
11
|
+
function azureRel(prefix, key) {
|
|
12
|
+
return prefix === "" ? key : key.slice(prefix.length + 1);
|
|
13
|
+
}
|
|
14
|
+
//#endregion
|
|
15
|
+
export { azureKey, azureRel };
|
|
16
|
+
|
|
17
|
+
//# sourceMappingURL=keys.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.mjs","names":[],"sources":["../src/keys.ts"],"sourcesContent":["/** Key join/split for Azure blob names. */\n\n/** Join an optional prefix with a storage path. */\nexport function azureKey(prefix: string, path: string): string {\n return prefix === \"\" ? path : `${prefix}/${path}`;\n}\n\n/** Strip a storage prefix back off a blob name. */\nexport function azureRel(prefix: string, key: string): string {\n return prefix === \"\" ? key : key.slice(prefix.length + 1);\n}\n"],"mappings":";;;;;;AAGA,SAAgB,SAAS,QAAgB,MAAsB;CAC7D,OAAO,WAAW,KAAK,OAAO,GAAG,OAAO,GAAG;AAC7C;;AAGA,SAAgB,SAAS,QAAgB,KAAqB;CAC5D,OAAO,WAAW,KAAK,MAAM,IAAI,MAAM,OAAO,SAAS,CAAC;AAC1D"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
//#region src/lifecycle.ts
|
|
5
|
+
/** Build the setup/health/teardown lifecycle for an Azure container. */
|
|
6
|
+
function buildAzureLifecycle(container, containerName) {
|
|
7
|
+
return {
|
|
8
|
+
setup: async () => {
|
|
9
|
+
await containerExists(container, containerName);
|
|
10
|
+
},
|
|
11
|
+
teardown: async () => {
|
|
12
|
+
await Promise.resolve();
|
|
13
|
+
},
|
|
14
|
+
health: async () => {
|
|
15
|
+
await containerExists(container, containerName);
|
|
16
|
+
return { ok: true };
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async function containerExists(container, containerName) {
|
|
21
|
+
const exists = container.exists;
|
|
22
|
+
if (typeof exists === "function") {
|
|
23
|
+
if (!await exists.call(container)) throw new Error(`Container not found: ${containerName}`);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const iterator = container.listBlobsFlat({ prefix: "" })[Symbol.asyncIterator]();
|
|
27
|
+
try {
|
|
28
|
+
await iterator.next();
|
|
29
|
+
} finally {
|
|
30
|
+
await iterator.return?.();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { buildAzureLifecycle };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=lifecycle.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle.mjs","names":[],"sources":["../src/lifecycle.ts"],"sourcesContent":["import type { ContainerClient } from \"@azure/storage-blob\";\nimport type { StorageAdapter } from \"@storyshelf/core/adapter/storage\";\n\n/** Build the setup/health/teardown lifecycle for an Azure container. */\nexport function buildAzureLifecycle(\n container: ContainerClient,\n containerName: string,\n): StorageAdapter[\"lifecycle\"] {\n return {\n setup: async () => {\n await containerExists(container, containerName);\n },\n teardown: async () => {\n // ContainerClient holds no closable handle — sockets are process-global.\n await Promise.resolve();\n },\n health: async () => {\n await containerExists(container, containerName);\n return { ok: true };\n },\n };\n}\n\nasync function containerExists(container: ContainerClient, containerName: string): Promise<void> {\n const exists = (container as unknown as { exists?: () => Promise<boolean> }).exists;\n if (typeof exists === \"function\") {\n const ok = await exists.call(container);\n if (!ok) {\n throw new Error(`Container not found: ${containerName}`);\n }\n return;\n }\n const iterator = container.listBlobsFlat({ prefix: \"\" })[Symbol.asyncIterator]();\n try {\n await iterator.next();\n } finally {\n await (iterator as { return?: () => Promise<unknown> }).return?.();\n }\n}\n"],"mappings":";;;;;AAIA,SAAgB,oBACd,WACA,eAC6B;CAC7B,OAAO;EACL,OAAO,YAAY;GACjB,MAAM,gBAAgB,WAAW,aAAa;EAChD;EACA,UAAU,YAAY;GAEpB,MAAM,QAAQ,QAAQ;EACxB;EACA,QAAQ,YAAY;GAClB,MAAM,gBAAgB,WAAW,aAAa;GAC9C,OAAO,EAAE,IAAI,KAAK;EACpB;CACF;AACF;AAEA,eAAe,gBAAgB,WAA4B,eAAsC;CAC/F,MAAM,SAAU,UAA6D;CAC7E,IAAI,OAAO,WAAW,YAAY;EAEhC,IAAI,CAAC,MADY,OAAO,KAAK,SAAS,GAEpC,MAAM,IAAI,MAAM,wBAAwB,eAAe;EAEzD;CACF;CACA,MAAM,WAAW,UAAU,cAAc,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC,OAAO,cAAc,CAAC;CAC/E,IAAI;EACF,MAAM,SAAS,KAAK;CACtB,UAAU;EACR,MAAO,SAAiD,SAAS;CACnE;AACF"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { collectDownload, isNotFound } from "./errors.mjs";
|
|
5
|
+
import { azureKey, azureRel } from "./keys.mjs";
|
|
6
|
+
//#region src/operations.ts
|
|
7
|
+
/** Buffered Azure blob CRUD with SDK-version duck-typing and async-iterator list. */
|
|
8
|
+
/** Download a blob, preferring `downloadToBuffer` when the SDK offers it. */
|
|
9
|
+
async function azureRead(ctx, path) {
|
|
10
|
+
const blob = ctx.container.getBlockBlobClient(azureKey(ctx.prefix, path));
|
|
11
|
+
if (typeof blob.downloadToBuffer === "function") return await blob.downloadToBuffer();
|
|
12
|
+
return await collectDownload(blob);
|
|
13
|
+
}
|
|
14
|
+
/** Upload a buffer, accepting either `uploadData` or `upload` SDK shapes. */
|
|
15
|
+
async function azureWrite(ctx, path, data) {
|
|
16
|
+
const withData = ctx.container.getBlockBlobClient(azureKey(ctx.prefix, path));
|
|
17
|
+
if (typeof withData.uploadData === "function") {
|
|
18
|
+
await withData.uploadData(data);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (typeof withData.upload === "function") {
|
|
22
|
+
await withData.upload(data, data.length);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
throw new Error("Azure Blob client does not support upload");
|
|
26
|
+
}
|
|
27
|
+
/** Delete a blob, treating missing blobs as success. */
|
|
28
|
+
async function azureDelete(ctx, path) {
|
|
29
|
+
const withDelete = ctx.container.getBlockBlobClient(azureKey(ctx.prefix, path));
|
|
30
|
+
if (typeof withDelete.deleteIfExists === "function") {
|
|
31
|
+
await withDelete.deleteIfExists();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
await withDelete.delete?.();
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (!isNotFound(error)) throw error;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Check whether a blob exists. */
|
|
41
|
+
async function azureExists(ctx, path) {
|
|
42
|
+
return await ctx.container.getBlockBlobClient(azureKey(ctx.prefix, path)).exists();
|
|
43
|
+
}
|
|
44
|
+
/** List blob names under a prefix, with the storage prefix stripped. */
|
|
45
|
+
async function azureList(ctx, listPrefix) {
|
|
46
|
+
const prefix = azureKey(ctx.prefix, listPrefix);
|
|
47
|
+
const blobs = [];
|
|
48
|
+
for await (const item of ctx.container.listBlobsFlat(prefix === "" ? void 0 : { prefix })) blobs.push(azureRel(ctx.prefix, item.name));
|
|
49
|
+
return blobs;
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { azureDelete, azureExists, azureList, azureRead, azureWrite };
|
|
53
|
+
|
|
54
|
+
//# sourceMappingURL=operations.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.mjs","names":[],"sources":["../src/operations.ts"],"sourcesContent":["import type { AzureContext } from \"./client.ts\";\nimport { collectDownload, isNotFound } from \"./errors.ts\";\nimport { azureKey, azureRel } from \"./keys.ts\";\n\n/** Buffered Azure blob CRUD with SDK-version duck-typing and async-iterator list. */\n\n/** Download a blob, preferring `downloadToBuffer` when the SDK offers it. */\nexport async function azureRead(ctx: AzureContext, path: string): Promise<Buffer> {\n const blob = ctx.container.getBlockBlobClient(azureKey(ctx.prefix, path));\n if (\n typeof (blob as unknown as { downloadToBuffer?: () => Promise<Buffer> }).downloadToBuffer ===\n \"function\"\n ) {\n return await (\n blob as unknown as { downloadToBuffer: () => Promise<Buffer> }\n ).downloadToBuffer();\n }\n return await collectDownload(blob);\n}\n\n/** Upload a buffer, accepting either `uploadData` or `upload` SDK shapes. */\nexport async function azureWrite(ctx: AzureContext, path: string, data: Buffer): Promise<void> {\n const blob = ctx.container.getBlockBlobClient(azureKey(ctx.prefix, path));\n const withData = blob as unknown as {\n uploadData?: (buffer: Buffer) => Promise<void>;\n upload?: (buffer: Buffer, length: number) => Promise<void>;\n };\n if (typeof withData.uploadData === \"function\") {\n await withData.uploadData(data);\n return;\n }\n if (typeof withData.upload === \"function\") {\n await withData.upload(data, data.length);\n return;\n }\n throw new Error(\"Azure Blob client does not support upload\");\n}\n\n/** Delete a blob, treating missing blobs as success. */\nexport async function azureDelete(ctx: AzureContext, path: string): Promise<void> {\n const blob = ctx.container.getBlockBlobClient(azureKey(ctx.prefix, path));\n const withDelete = blob as unknown as {\n deleteIfExists?: () => Promise<void>;\n delete?: () => Promise<void>;\n };\n if (typeof withDelete.deleteIfExists === \"function\") {\n await withDelete.deleteIfExists();\n return;\n }\n try {\n await withDelete.delete?.();\n } catch (error) {\n if (!isNotFound(error)) {\n throw error;\n }\n }\n}\n\n/** Check whether a blob exists. */\nexport async function azureExists(ctx: AzureContext, path: string): Promise<boolean> {\n const blob = ctx.container.getBlockBlobClient(azureKey(ctx.prefix, path));\n return await blob.exists();\n}\n\n/** List blob names under a prefix, with the storage prefix stripped. */\nexport async function azureList(ctx: AzureContext, listPrefix: string): Promise<string[]> {\n const prefix = azureKey(ctx.prefix, listPrefix);\n const blobs: string[] = [];\n for await (const item of ctx.container.listBlobsFlat(prefix === \"\" ? undefined : { prefix })) {\n blobs.push(azureRel(ctx.prefix, item.name));\n }\n return blobs;\n}\n"],"mappings":";;;;;;;;AAOA,eAAsB,UAAU,KAAmB,MAA+B;CAChF,MAAM,OAAO,IAAI,UAAU,mBAAmB,SAAS,IAAI,QAAQ,IAAI,CAAC;CACxE,IACE,OAAQ,KAAiE,qBACzE,YAEA,OAAO,MACL,KACA,iBAAiB;CAErB,OAAO,MAAM,gBAAgB,IAAI;AACnC;;AAGA,eAAsB,WAAW,KAAmB,MAAc,MAA6B;CAE7F,MAAM,WADO,IAAI,UAAU,mBAAmB,SAAS,IAAI,QAAQ,IAAI,CACnD;CAIpB,IAAI,OAAO,SAAS,eAAe,YAAY;EAC7C,MAAM,SAAS,WAAW,IAAI;EAC9B;CACF;CACA,IAAI,OAAO,SAAS,WAAW,YAAY;EACzC,MAAM,SAAS,OAAO,MAAM,KAAK,MAAM;EACvC;CACF;CACA,MAAM,IAAI,MAAM,2CAA2C;AAC7D;;AAGA,eAAsB,YAAY,KAAmB,MAA6B;CAEhF,MAAM,aADO,IAAI,UAAU,mBAAmB,SAAS,IAAI,QAAQ,IAAI,CACjD;CAItB,IAAI,OAAO,WAAW,mBAAmB,YAAY;EACnD,MAAM,WAAW,eAAe;EAChC;CACF;CACA,IAAI;EACF,MAAM,WAAW,SAAS;CAC5B,SAAS,OAAO;EACd,IAAI,CAAC,WAAW,KAAK,GACnB,MAAM;CAEV;AACF;;AAGA,eAAsB,YAAY,KAAmB,MAAgC;CAEnF,OAAO,MADM,IAAI,UAAU,mBAAmB,SAAS,IAAI,QAAQ,IAAI,CACvD,CAAC,CAAC,OAAO;AAC3B;;AAGA,eAAsB,UAAU,KAAmB,YAAuC;CACxF,MAAM,SAAS,SAAS,IAAI,QAAQ,UAAU;CAC9C,MAAM,QAAkB,CAAC;CACzB,WAAW,MAAM,QAAQ,IAAI,UAAU,cAAc,WAAW,KAAK,KAAA,IAAY,EAAE,OAAO,CAAC,GACzF,MAAM,KAAK,SAAS,IAAI,QAAQ,KAAK,IAAI,CAAC;CAE5C,OAAO;AACT"}
|
package/dist/streams.mjs
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { collectStream, deleteBlobQuiet } from "./errors.mjs";
|
|
5
|
+
import { azureKey } from "./keys.mjs";
|
|
6
|
+
import { azureExists, azureWrite } from "./operations.mjs";
|
|
7
|
+
import { Readable } from "node:stream";
|
|
8
|
+
//#region src/streams.ts
|
|
9
|
+
/** Streamed Azure blob reads and writes, with failure cleanup. */
|
|
10
|
+
/** Upload a readable, streaming when the SDK supports it and buffering otherwise. */
|
|
11
|
+
async function azureWriteStream(ctx, path, stream) {
|
|
12
|
+
const blob = ctx.container.getBlockBlobClient(azureKey(ctx.prefix, path));
|
|
13
|
+
if (await tryAzureStreamUpload(blob, stream)) return;
|
|
14
|
+
await writeStreamBuffered(ctx, path, stream, blob);
|
|
15
|
+
}
|
|
16
|
+
/** Download a blob as a readable, rejecting when the blob is missing. */
|
|
17
|
+
async function azureReadStream(ctx, path) {
|
|
18
|
+
if (!await azureExists(ctx, path)) throw new Error(`No object at "${path}"`);
|
|
19
|
+
const body = (await ctx.container.getBlockBlobClient(azureKey(ctx.prefix, path)).download()).readableStreamBody;
|
|
20
|
+
if (!body) return Readable.from([]);
|
|
21
|
+
return body;
|
|
22
|
+
}
|
|
23
|
+
async function tryAzureStreamUpload(blob, stream) {
|
|
24
|
+
const withStream = blob;
|
|
25
|
+
if (typeof withStream.uploadStream !== "function") return false;
|
|
26
|
+
try {
|
|
27
|
+
await withStream.uploadStream(stream);
|
|
28
|
+
return true;
|
|
29
|
+
} catch (error) {
|
|
30
|
+
await deleteBlobQuiet(blob);
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function writeStreamBuffered(ctx, path, stream, blob) {
|
|
35
|
+
try {
|
|
36
|
+
const data = await collectStream(stream);
|
|
37
|
+
await azureWrite(ctx, path, data);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
await deleteBlobQuiet(blob);
|
|
40
|
+
throw error;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
//#endregion
|
|
44
|
+
export { azureReadStream, azureWriteStream };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=streams.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"streams.mjs","names":[],"sources":["../src/streams.ts"],"sourcesContent":["import type { ContainerClient } from \"@azure/storage-blob\";\nimport { Readable } from \"node:stream\";\nimport type { AzureContext } from \"./client.ts\";\nimport { collectStream, deleteBlobQuiet } from \"./errors.ts\";\nimport { azureKey } from \"./keys.ts\";\nimport { azureExists, azureWrite } from \"./operations.ts\";\n\n/** Streamed Azure blob reads and writes, with failure cleanup. */\n\n/** Upload a readable, streaming when the SDK supports it and buffering otherwise. */\nexport async function azureWriteStream(\n ctx: AzureContext,\n path: string,\n stream: Readable,\n): Promise<void> {\n const blob = ctx.container.getBlockBlobClient(azureKey(ctx.prefix, path));\n if (await tryAzureStreamUpload(blob, stream)) {\n return;\n }\n await writeStreamBuffered(ctx, path, stream, blob);\n}\n\n/** Download a blob as a readable, rejecting when the blob is missing. */\nexport async function azureReadStream(ctx: AzureContext, path: string): Promise<Readable> {\n const exists = await azureExists(ctx, path);\n if (!exists) {\n throw new Error(`No object at \"${path}\"`);\n }\n const blob = ctx.container.getBlockBlobClient(azureKey(ctx.prefix, path));\n const response = await blob.download();\n const body = response.readableStreamBody as unknown as Readable | undefined;\n if (!body) {\n return Readable.from([]);\n }\n return body;\n}\n\nasync function tryAzureStreamUpload(\n blob: ReturnType<ContainerClient[\"getBlockBlobClient\"]>,\n stream: Readable,\n): Promise<boolean> {\n const withStream = blob as unknown as { uploadStream?: (s: Readable) => Promise<void> };\n if (typeof withStream.uploadStream !== \"function\") {\n return false;\n }\n try {\n await withStream.uploadStream(stream);\n return true;\n } catch (error) {\n await deleteBlobQuiet(blob);\n throw error;\n }\n}\n\nasync function writeStreamBuffered(\n ctx: AzureContext,\n path: string,\n stream: Readable,\n blob: ReturnType<ContainerClient[\"getBlockBlobClient\"]>,\n): Promise<void> {\n try {\n const data = await collectStream(stream);\n await azureWrite(ctx, path, data);\n } catch (error) {\n await deleteBlobQuiet(blob);\n throw error;\n }\n}\n"],"mappings":";;;;;;;;;;AAUA,eAAsB,iBACpB,KACA,MACA,QACe;CACf,MAAM,OAAO,IAAI,UAAU,mBAAmB,SAAS,IAAI,QAAQ,IAAI,CAAC;CACxE,IAAI,MAAM,qBAAqB,MAAM,MAAM,GACzC;CAEF,MAAM,oBAAoB,KAAK,MAAM,QAAQ,IAAI;AACnD;;AAGA,eAAsB,gBAAgB,KAAmB,MAAiC;CAExF,IAAI,CAAC,MADgB,YAAY,KAAK,IAAI,GAExC,MAAM,IAAI,MAAM,iBAAiB,KAAK,EAAE;CAI1C,MAAM,QAAO,MAFA,IAAI,UAAU,mBAAmB,SAAS,IAAI,QAAQ,IAAI,CAC7C,CAAC,CAAC,SAAS,EAAA,CACf;CACtB,IAAI,CAAC,MACH,OAAO,SAAS,KAAK,CAAC,CAAC;CAEzB,OAAO;AACT;AAEA,eAAe,qBACb,MACA,QACkB;CAClB,MAAM,aAAa;CACnB,IAAI,OAAO,WAAW,iBAAiB,YACrC,OAAO;CAET,IAAI;EACF,MAAM,WAAW,aAAa,MAAM;EACpC,OAAO;CACT,SAAS,OAAO;EACd,MAAM,gBAAgB,IAAI;EAC1B,MAAM;CACR;AACF;AAEA,eAAe,oBACb,KACA,MACA,QACA,MACe;CACf,IAAI;EACF,MAAM,OAAO,MAAM,cAAc,MAAM;EACvC,MAAM,WAAW,KAAK,MAAM,IAAI;CAClC,SAAS,OAAO;EACd,MAAM,gBAAgB,IAAI;EAC1B,MAAM;CACR;AACF"}
|
package/dist/types.d.mts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { ContainerClient } from "@azure/storage-blob";
|
|
4
|
+
//#region src/types.d.ts
|
|
5
|
+
/** Options for configuring an Azure Blob Storage adapter. */
|
|
6
|
+
interface AzureStorageOptions {
|
|
7
|
+
/** Azure container name. */
|
|
8
|
+
container: string;
|
|
9
|
+
/** Optional blob prefix applied to all stored objects. */
|
|
10
|
+
prefix?: string;
|
|
11
|
+
/** Pre-configured container client. */
|
|
12
|
+
client?: ContainerClient;
|
|
13
|
+
/** Azure Storage connection string. */
|
|
14
|
+
connectionString?: string;
|
|
15
|
+
/** Storage account name. */
|
|
16
|
+
accountName?: string;
|
|
17
|
+
/** Storage account key. */
|
|
18
|
+
accountKey?: string;
|
|
19
|
+
/** Blob service URL (e.g. https://myaccount.blob.core.windows.net). */
|
|
20
|
+
accountUrl?: string;
|
|
21
|
+
/** SAS token (with or without leading `?`). */
|
|
22
|
+
sasToken?: string;
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { AzureStorageOptions };
|
|
26
|
+
//# sourceMappingURL=types.d.mts.map
|
package/package.json
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@storyshelf/storage-azure",
|
|
3
|
+
"version": "0.5.3",
|
|
4
|
+
"description": "Azure Blob Storage adapter for StoryShelf.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"azure",
|
|
7
|
+
"blob-storage",
|
|
8
|
+
"self-hosted",
|
|
9
|
+
"storage",
|
|
10
|
+
"storybook"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/GuptaSiddhant/StoryShelf#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/GuptaSiddhant/StoryShelf/issues"
|
|
15
|
+
},
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"author": {
|
|
18
|
+
"name": "Siddhant Gupta",
|
|
19
|
+
"url": "https://guptasiddhant.com"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/GuptaSiddhant/StoryShelf.git",
|
|
24
|
+
"directory": "packages/storage-azure"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist"
|
|
28
|
+
],
|
|
29
|
+
"type": "module",
|
|
30
|
+
"sideEffects": false,
|
|
31
|
+
"types": "./dist/index.d.mts",
|
|
32
|
+
"exports": {
|
|
33
|
+
".": {
|
|
34
|
+
"source": "./src/index.ts",
|
|
35
|
+
"default": "./dist/index.mjs"
|
|
36
|
+
},
|
|
37
|
+
"./package.json": "./package.json"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"exports": {
|
|
41
|
+
".": "./dist/index.mjs",
|
|
42
|
+
"./package.json": "./package.json"
|
|
43
|
+
},
|
|
44
|
+
"access": "public"
|
|
45
|
+
},
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsdown",
|
|
48
|
+
"dev": "tsdown -w",
|
|
49
|
+
"fmt": "oxfmt ./src",
|
|
50
|
+
"lint": "oxlint --type-aware --type-check ./src",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"prepublishOnly": "nub run build"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@azure/storage-blob": "^12.26.0",
|
|
56
|
+
"@storyshelf/core": "0.5.3"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/node": "^26.0.0",
|
|
60
|
+
"@vitest/coverage-v8": "^4.0.0",
|
|
61
|
+
"oxfmt": "^0.65.0",
|
|
62
|
+
"oxlint": "^1.80.0",
|
|
63
|
+
"oxlint-tsgolint": "^7.0.2001",
|
|
64
|
+
"tsdown": "^0.22.14",
|
|
65
|
+
"typescript": "^7.0.2",
|
|
66
|
+
"vitest": "^5.0.0"
|
|
67
|
+
},
|
|
68
|
+
"jsr": {
|
|
69
|
+
"runtimeCompat": {
|
|
70
|
+
"node": true,
|
|
71
|
+
"deno": true,
|
|
72
|
+
"browser": false,
|
|
73
|
+
"workerd": false,
|
|
74
|
+
"bun": true
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|