@uploadista/data-store-azure 0.0.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/.turbo/turbo-build.log +5 -0
- package/.turbo/turbo-check.log +5 -0
- package/LICENSE +21 -0
- package/README.md +570 -0
- package/dist/azure-store.d.ts +67 -0
- package/dist/azure-store.d.ts.map +1 -0
- package/dist/azure-store.js +725 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1 -0
- package/package.json +33 -0
- package/src/azure-store.ts +1083 -0
- package/src/index.ts +1 -0
- package/tsconfig.json +20 -0
- package/tsconfig.tsbuildinfo +1 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 uploadista
|
|
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,570 @@
|
|
|
1
|
+
# @uploadista/data-store-azure
|
|
2
|
+
|
|
3
|
+
Azure Blob Storage data store for Uploadista - Store files in Microsoft Azure.
|
|
4
|
+
|
|
5
|
+
Provides cross-platform Azure Blob Storage integration with multiple authentication methods, resumable uploads, and comprehensive error handling. Works in Node.js, browsers, and edge environments.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Cross-Platform** - Node.js, browsers, Cloudflare Workers, Azure Functions
|
|
10
|
+
- **Multiple Auth Methods** - SAS URL, OAuth, Connection String, Shared Key
|
|
11
|
+
- **Resumable Uploads** - Resume failed transfers without re-uploading
|
|
12
|
+
- **Block Management** - Handles Azure's 50K block limit transparently
|
|
13
|
+
- **Custom Metadata** - Attach metadata to blobs
|
|
14
|
+
- **Full Observability** - Metrics, logging, and distributed tracing
|
|
15
|
+
- **TypeScript** - Full type safety with comprehensive JSDoc
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @uploadista/data-store-azure @azure/storage-blob @uploadista/core
|
|
21
|
+
# or
|
|
22
|
+
pnpm add @uploadista/data-store-azure @azure/storage-blob @uploadista/core
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Requirements
|
|
26
|
+
|
|
27
|
+
- Node.js 18+ (for production server)
|
|
28
|
+
- Azure Storage Account
|
|
29
|
+
- Azure Container
|
|
30
|
+
- TypeScript 5.0+ (optional but recommended)
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
### 1. Choose Authentication Method
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// Option 1: SAS URL (recommended for browsers/edge)
|
|
38
|
+
import { azureStore } from "@uploadista/data-store-azure";
|
|
39
|
+
|
|
40
|
+
const store = azureStore({
|
|
41
|
+
deliveryUrl: "https://myaccount.blob.core.windows.net",
|
|
42
|
+
sasUrl: "https://myaccount.blob.core.windows.net?sv=2022-11-02&ss=b&sp=rcwd&se=2024-12-31T23:59:59Z&sig=...",
|
|
43
|
+
containerName: "uploads",
|
|
44
|
+
kvStore: memoryKvStore,
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// Option 2: OAuth (recommended for production)
|
|
50
|
+
import { DefaultAzureCredential } from "@azure/identity";
|
|
51
|
+
import { azureStore } from "@uploadista/data-store-azure";
|
|
52
|
+
|
|
53
|
+
const credential = new DefaultAzureCredential();
|
|
54
|
+
|
|
55
|
+
const store = azureStore({
|
|
56
|
+
deliveryUrl: "https://myaccount.blob.core.windows.net",
|
|
57
|
+
accountName: "myaccount",
|
|
58
|
+
credential,
|
|
59
|
+
containerName: "uploads",
|
|
60
|
+
kvStore: memoryKvStore,
|
|
61
|
+
});
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 2. Configure Azure Credentials
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
# Option 1: Environment variables (DefaultAzureCredential)
|
|
68
|
+
export AZURE_TENANT_ID=your-tenant-id
|
|
69
|
+
export AZURE_CLIENT_ID=your-client-id
|
|
70
|
+
export AZURE_CLIENT_SECRET=your-client-secret
|
|
71
|
+
|
|
72
|
+
# Option 2: Connection string
|
|
73
|
+
export AZURE_STORAGE_CONNECTION_STRING="DefaultEndpointsProtocol=https;AccountName=...;AccountKey=...;"
|
|
74
|
+
|
|
75
|
+
# Option 3: SAS URL
|
|
76
|
+
export AZURE_STORAGE_SAS_URL="https://account.blob.core.windows.net?sv=2022-11-02&..."
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### 3. Use in Server
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { createFastifyUploadistaAdapter } from "@uploadista/adapters-fastify";
|
|
83
|
+
import { azureStore } from "@uploadista/data-store-azure";
|
|
84
|
+
import { redisKvStore } from "@uploadista/kv-store-redis";
|
|
85
|
+
|
|
86
|
+
const adapter = await createFastifyUploadistaAdapter({
|
|
87
|
+
baseUrl: "uploadista",
|
|
88
|
+
dataStore: azureStore({
|
|
89
|
+
deliveryUrl: process.env.AZURE_STORAGE_URL!,
|
|
90
|
+
containerName: "uploads",
|
|
91
|
+
credential: new DefaultAzureCredential(),
|
|
92
|
+
accountName: process.env.AZURE_ACCOUNT_NAME!,
|
|
93
|
+
kvStore: redisKvStore,
|
|
94
|
+
}),
|
|
95
|
+
kvStore: redisKvStore,
|
|
96
|
+
flows: createFlowsEffect,
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Configuration
|
|
101
|
+
|
|
102
|
+
### `AzureStoreOptions`
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
type AzureStoreOptions = {
|
|
106
|
+
// Required
|
|
107
|
+
deliveryUrl: string; // URL for accessing blobs
|
|
108
|
+
containerName: string; // Container name
|
|
109
|
+
kvStore: KvStore<UploadFile>; // Metadata store
|
|
110
|
+
|
|
111
|
+
// Block management (optional)
|
|
112
|
+
blockSize?: number; // Preferred block size (1B-4000MiB)
|
|
113
|
+
minBlockSize?: number; // Minimum block size (default: 1KB)
|
|
114
|
+
maxBlocks?: number; // Default: 50,000 (Azure limit)
|
|
115
|
+
maxConcurrentBlockUploads?: number; // Default: 60
|
|
116
|
+
expirationPeriodInMilliseconds?: number; // Default: 1 week
|
|
117
|
+
|
|
118
|
+
// Authentication - choose one:
|
|
119
|
+
sasUrl?: string; // SAS URL (for all environments)
|
|
120
|
+
credential?: TokenCredential; // OAuth (recommended for production)
|
|
121
|
+
connectionString?: string; // Connection string (all environments)
|
|
122
|
+
accountKey?: string; // Shared key (Node.js only, deprecated)
|
|
123
|
+
|
|
124
|
+
// With OAuth, also specify:
|
|
125
|
+
accountName?: string; // Storage account name
|
|
126
|
+
};
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Authentication Methods
|
|
130
|
+
|
|
131
|
+
### 1. SAS URL (Best for Browsers/Edge)
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// Generate SAS URL in Azure Portal or CLI
|
|
135
|
+
// Storage Account → Containers → shared access tokens
|
|
136
|
+
|
|
137
|
+
const store = azureStore({
|
|
138
|
+
deliveryUrl: "https://myaccount.blob.core.windows.net",
|
|
139
|
+
sasUrl: "https://myaccount.blob.core.windows.net?sv=2022-11-02&ss=b&srt=sco&sp=rwdlacupx&se=2024-12-31T23:59:59Z&st=2024-01-01T00:00:00Z&spr=https&sig=...",
|
|
140
|
+
containerName: "uploads",
|
|
141
|
+
kvStore,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Works in:
|
|
145
|
+
// ✓ Node.js
|
|
146
|
+
// ✓ Browsers
|
|
147
|
+
// ✓ Cloudflare Workers
|
|
148
|
+
// ✓ Azure Functions
|
|
149
|
+
// ✓ Edge environments
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### 2. OAuth Token Credential (Best for Production)
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { DefaultAzureCredential } from "@azure/identity";
|
|
156
|
+
|
|
157
|
+
// Automatically uses (in order):
|
|
158
|
+
// 1. Environment variables (AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET)
|
|
159
|
+
// 2. Managed Identity (if running on Azure VM, App Service, etc.)
|
|
160
|
+
// 3. Azure CLI credentials
|
|
161
|
+
// 4. Visual Studio Code credentials
|
|
162
|
+
|
|
163
|
+
const credential = new DefaultAzureCredential();
|
|
164
|
+
|
|
165
|
+
const store = azureStore({
|
|
166
|
+
deliveryUrl: "https://myaccount.blob.core.windows.net",
|
|
167
|
+
accountName: "myaccount",
|
|
168
|
+
credential,
|
|
169
|
+
containerName: "uploads",
|
|
170
|
+
kvStore,
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Works in:
|
|
174
|
+
// ✓ Node.js
|
|
175
|
+
// ✓ Azure services (VM, App Service, Functions, etc.)
|
|
176
|
+
// ✓ With Azure AD integration
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### 3. Connection String
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
const store = azureStore({
|
|
183
|
+
deliveryUrl: "https://myaccount.blob.core.windows.net",
|
|
184
|
+
connectionString: "DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=your-key;EndpointSuffix=core.windows.net",
|
|
185
|
+
containerName: "uploads",
|
|
186
|
+
kvStore,
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// Works in:
|
|
190
|
+
// ✓ Node.js
|
|
191
|
+
// ✓ Browsers (if hosted server-side)
|
|
192
|
+
// ✓ Azure Functions
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 4. Shared Key (Legacy - Node.js Only)
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// ⚠️ Deprecated - only for backwards compatibility
|
|
199
|
+
// Use OAuth or SAS URL instead
|
|
200
|
+
|
|
201
|
+
const store = azureStore({
|
|
202
|
+
deliveryUrl: "https://myaccount.blob.core.windows.net",
|
|
203
|
+
accountName: "myaccount",
|
|
204
|
+
accountKey: "your-account-key",
|
|
205
|
+
containerName: "uploads",
|
|
206
|
+
kvStore,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
// Only works in Node.js
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Azure Setup Guide
|
|
213
|
+
|
|
214
|
+
### 1. Create Storage Account
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
# Azure CLI
|
|
218
|
+
az storage account create \
|
|
219
|
+
--name myaccount \
|
|
220
|
+
--resource-group my-rg \
|
|
221
|
+
--location eastus \
|
|
222
|
+
--sku Standard_LRS
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### 2. Create Container
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
az storage container create \
|
|
229
|
+
--name uploads \
|
|
230
|
+
--account-name myaccount \
|
|
231
|
+
--auth-mode login
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### 3. Generate SAS URL
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
# Via Azure CLI
|
|
238
|
+
az storage container generate-sas \
|
|
239
|
+
--name uploads \
|
|
240
|
+
--account-name myaccount \
|
|
241
|
+
--permissions acdlrw \
|
|
242
|
+
--expiry 2024-12-31T23:59:59Z \
|
|
243
|
+
--https-only
|
|
244
|
+
|
|
245
|
+
# Returns: sv=2022-11-02&ss=b&srt=sco&sp=rwdlacupx&se=2024-12-31T23:59:59Z&...
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### 4. Create Service Principal (OAuth)
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
# Create service principal
|
|
252
|
+
az ad sp create-for-rbac \
|
|
253
|
+
--name uploadista-service \
|
|
254
|
+
--role "Storage Blob Data Contributor" \
|
|
255
|
+
--scopes /subscriptions/YOUR_SUBSCRIPTION_ID/resourceGroups/my-rg/providers/Microsoft.Storage/storageAccounts/myaccount
|
|
256
|
+
|
|
257
|
+
# Output includes:
|
|
258
|
+
# - appId (AZURE_CLIENT_ID)
|
|
259
|
+
# - password (AZURE_CLIENT_SECRET)
|
|
260
|
+
# - tenant (AZURE_TENANT_ID)
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### 5. Configure CORS (Optional)
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
az storage cors add \
|
|
267
|
+
--services b \
|
|
268
|
+
--methods GET HEAD PUT POST DELETE OPTIONS \
|
|
269
|
+
--origins "https://myapp.com" \
|
|
270
|
+
--allowed-headers "*" \
|
|
271
|
+
--exposed-headers "*" \
|
|
272
|
+
--max-age 3600 \
|
|
273
|
+
--account-name myaccount
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## Complete Server Example
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
import Fastify from "fastify";
|
|
280
|
+
import WebSocket from "@fastify/websocket";
|
|
281
|
+
import JwT from "@fastify/jwt";
|
|
282
|
+
import { DefaultAzureCredential } from "@azure/identity";
|
|
283
|
+
import { createFastifyUploadistaAdapter } from "@uploadista/adapters-fastify";
|
|
284
|
+
import { azureStore } from "@uploadista/data-store-azure";
|
|
285
|
+
import { redisKvStore } from "@uploadista/kv-store-redis";
|
|
286
|
+
import { webSocketEventEmitter } from "@uploadista/event-emitter-websocket";
|
|
287
|
+
import { memoryEventBroadcaster } from "@uploadista/event-broadcaster-memory";
|
|
288
|
+
|
|
289
|
+
const fastify = Fastify({ logger: true });
|
|
290
|
+
|
|
291
|
+
await fastify.register(JwT, { secret: process.env.JWT_SECRET! });
|
|
292
|
+
await fastify.register(WebSocket);
|
|
293
|
+
|
|
294
|
+
// Create Azure store
|
|
295
|
+
const credential = new DefaultAzureCredential();
|
|
296
|
+
|
|
297
|
+
const azureDataStore = azureStore({
|
|
298
|
+
deliveryUrl: process.env.AZURE_STORAGE_URL!,
|
|
299
|
+
accountName: process.env.AZURE_ACCOUNT_NAME!,
|
|
300
|
+
credential,
|
|
301
|
+
containerName: process.env.AZURE_CONTAINER || "uploads",
|
|
302
|
+
kvStore: redisKvStore,
|
|
303
|
+
blockSize: parseInt(process.env.AZURE_BLOCK_SIZE || "4194304"), // 4MB
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// Create adapter
|
|
307
|
+
const adapter = await createFastifyUploadistaAdapter({
|
|
308
|
+
baseUrl: "uploadista",
|
|
309
|
+
dataStore: azureDataStore,
|
|
310
|
+
kvStore: redisKvStore,
|
|
311
|
+
eventEmitter: webSocketEventEmitter,
|
|
312
|
+
eventBroadcaster: memoryEventBroadcaster,
|
|
313
|
+
flows: createFlowsEffect,
|
|
314
|
+
authMiddleware: async (req, reply) => {
|
|
315
|
+
try {
|
|
316
|
+
await req.jwtVerify();
|
|
317
|
+
return {
|
|
318
|
+
clientId: (req.user as any).sub,
|
|
319
|
+
permissions: ["upload:create"],
|
|
320
|
+
};
|
|
321
|
+
} catch {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// Routes
|
|
328
|
+
fastify.all(`/${adapter.baseUrl}/*`, (req, res) => adapter.handler(req, res));
|
|
329
|
+
fastify.get("/ws", { websocket: true }, (socket, req) => {
|
|
330
|
+
adapter.websocketHandler(socket, req);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
await fastify.listen({ port: 3000 });
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
## Performance Tuning
|
|
337
|
+
|
|
338
|
+
### Block Size Strategy
|
|
339
|
+
|
|
340
|
+
Azure limits uploads to 50,000 blocks. Block size is calculated automatically:
|
|
341
|
+
|
|
342
|
+
```typescript
|
|
343
|
+
// For typical files (<4GB)
|
|
344
|
+
const store = azureStore({
|
|
345
|
+
deliveryUrl,
|
|
346
|
+
accountName,
|
|
347
|
+
credential,
|
|
348
|
+
containerName: "uploads",
|
|
349
|
+
kvStore,
|
|
350
|
+
blockSize: 4 * 1024 * 1024, // 4MB (default)
|
|
351
|
+
maxConcurrentBlockUploads: 10,
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
// For large files (>4GB, up to 200GB)
|
|
355
|
+
const largeFileStore = azureStore({
|
|
356
|
+
deliveryUrl,
|
|
357
|
+
accountName,
|
|
358
|
+
credential,
|
|
359
|
+
containerName: "uploads",
|
|
360
|
+
kvStore,
|
|
361
|
+
blockSize: 20 * 1024 * 1024, // 20MB
|
|
362
|
+
maxConcurrentBlockUploads: 5,
|
|
363
|
+
});
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
## Environment Configuration
|
|
367
|
+
|
|
368
|
+
### .env File
|
|
369
|
+
|
|
370
|
+
```env
|
|
371
|
+
# Azure Configuration
|
|
372
|
+
AZURE_STORAGE_URL=https://myaccount.blob.core.windows.net
|
|
373
|
+
AZURE_ACCOUNT_NAME=myaccount
|
|
374
|
+
AZURE_CONTAINER=uploads
|
|
375
|
+
AZURE_BLOCK_SIZE=4194304
|
|
376
|
+
|
|
377
|
+
# OAuth (DefaultAzureCredential)
|
|
378
|
+
AZURE_TENANT_ID=your-tenant-id
|
|
379
|
+
AZURE_CLIENT_ID=your-client-id
|
|
380
|
+
AZURE_CLIENT_SECRET=your-client-secret
|
|
381
|
+
|
|
382
|
+
# Or Connection String
|
|
383
|
+
AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...;
|
|
384
|
+
|
|
385
|
+
# Or SAS URL
|
|
386
|
+
AZURE_STORAGE_SAS_URL=https://myaccount.blob.core.windows.net?sv=2022-11-02&...
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
## Cross-Platform Authentication
|
|
390
|
+
|
|
391
|
+
Use the right auth method for your environment:
|
|
392
|
+
|
|
393
|
+
| Environment | Recommended | Alternative |
|
|
394
|
+
|-------------|------------|--------------|
|
|
395
|
+
| Node.js Backend | OAuth (DefaultAzureCredential) | Connection String |
|
|
396
|
+
| Browser/SPA | SAS URL | Cannot use OAuth directly |
|
|
397
|
+
| Azure VM/App Service | Managed Identity (via DefaultAzureCredential) | OAuth |
|
|
398
|
+
| Cloudflare Workers | SAS URL | Connection String |
|
|
399
|
+
| Azure Functions | Managed Identity | OAuth |
|
|
400
|
+
|
|
401
|
+
## Migration from Shared Key
|
|
402
|
+
|
|
403
|
+
If using deprecated shared key auth:
|
|
404
|
+
|
|
405
|
+
```typescript
|
|
406
|
+
// Old way (deprecated)
|
|
407
|
+
const oldStore = azureStore({
|
|
408
|
+
accountName: "myaccount",
|
|
409
|
+
accountKey: "...",
|
|
410
|
+
containerName: "uploads",
|
|
411
|
+
kvStore,
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Migrate to OAuth
|
|
415
|
+
import { DefaultAzureCredential } from "@azure/identity";
|
|
416
|
+
|
|
417
|
+
const newStore = azureStore({
|
|
418
|
+
deliveryUrl: "https://myaccount.blob.core.windows.net",
|
|
419
|
+
accountName: "myaccount",
|
|
420
|
+
credential: new DefaultAzureCredential(),
|
|
421
|
+
containerName: "uploads",
|
|
422
|
+
kvStore,
|
|
423
|
+
});
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
## Error Handling
|
|
427
|
+
|
|
428
|
+
Common Azure errors and solutions:
|
|
429
|
+
|
|
430
|
+
| Error | Cause | Solution |
|
|
431
|
+
|-------|-------|----------|
|
|
432
|
+
| CONTAINER_NOT_FOUND | Container doesn't exist | Create container via Azure CLI or Portal |
|
|
433
|
+
| AUTHENTICATION_FAILED | Invalid credentials | Verify auth method and credentials |
|
|
434
|
+
| INVALID_BLOCK_SIZE | Block too large | Azure limit is 4GB per block, max 50K blocks |
|
|
435
|
+
| REQUEST_TIMEOUT | Upload timeout | Reduce block size or increase network timeout |
|
|
436
|
+
| PAYLOAD_TOO_LARGE | File exceeds limit | Azure Blob max is 200GB (50K × 4MB blocks) |
|
|
437
|
+
|
|
438
|
+
## Monitoring & Observability
|
|
439
|
+
|
|
440
|
+
```typescript
|
|
441
|
+
// Metrics automatically tracked:
|
|
442
|
+
// - azure.upload.started
|
|
443
|
+
// - azure.upload.progress
|
|
444
|
+
// - azure.upload.completed
|
|
445
|
+
// - azure.upload.failed
|
|
446
|
+
// - azure.block.uploaded
|
|
447
|
+
// - azure.metadata.operations
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
## Deployment Examples
|
|
451
|
+
|
|
452
|
+
### Docker
|
|
453
|
+
|
|
454
|
+
```dockerfile
|
|
455
|
+
FROM node:20-alpine
|
|
456
|
+
WORKDIR /app
|
|
457
|
+
COPY package*.json ./
|
|
458
|
+
RUN npm ci --only=production
|
|
459
|
+
COPY dist ./dist
|
|
460
|
+
|
|
461
|
+
ENV NODE_ENV=production
|
|
462
|
+
|
|
463
|
+
EXPOSE 3000
|
|
464
|
+
CMD ["node", "dist/server.js"]
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
### Azure Container Instances
|
|
468
|
+
|
|
469
|
+
```yaml
|
|
470
|
+
apiVersion: 2021-07-01
|
|
471
|
+
type: Microsoft.ContainerInstance/containerGroups
|
|
472
|
+
name: uploadista-server
|
|
473
|
+
properties:
|
|
474
|
+
containers:
|
|
475
|
+
- name: app
|
|
476
|
+
properties:
|
|
477
|
+
image: myregistry.azurecr.io/uploadista:latest
|
|
478
|
+
ports:
|
|
479
|
+
- port: 3000
|
|
480
|
+
environmentVariables:
|
|
481
|
+
- name: AZURE_ACCOUNT_NAME
|
|
482
|
+
value: myaccount
|
|
483
|
+
- name: AZURE_CONTAINER
|
|
484
|
+
value: uploads
|
|
485
|
+
resources:
|
|
486
|
+
requests:
|
|
487
|
+
cpu: 1
|
|
488
|
+
memoryInGb: 2
|
|
489
|
+
osType: Linux
|
|
490
|
+
restartPolicy: Always
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
### Azure App Service
|
|
494
|
+
|
|
495
|
+
```bash
|
|
496
|
+
# Create App Service plan
|
|
497
|
+
az appservice plan create \
|
|
498
|
+
--name myplan \
|
|
499
|
+
--resource-group my-rg \
|
|
500
|
+
--sku B2
|
|
501
|
+
|
|
502
|
+
# Create web app
|
|
503
|
+
az webapp create \
|
|
504
|
+
--resource-group my-rg \
|
|
505
|
+
--plan myplan \
|
|
506
|
+
--name uploadista-server \
|
|
507
|
+
--runtime "node|20"
|
|
508
|
+
|
|
509
|
+
# Configure app settings
|
|
510
|
+
az webapp config appsettings set \
|
|
511
|
+
--name uploadista-server \
|
|
512
|
+
--resource-group my-rg \
|
|
513
|
+
--settings AZURE_ACCOUNT_NAME=myaccount AZURE_CONTAINER=uploads
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
## Related Packages
|
|
517
|
+
|
|
518
|
+
- **[@uploadista/data-store-s3](../s3/)** - AWS S3 storage
|
|
519
|
+
- **[@uploadista/data-store-gcs](../gcs/)** - Google Cloud Storage
|
|
520
|
+
- **[@uploadista/data-store-filesystem](../filesystem/)** - Local filesystem
|
|
521
|
+
- **[@uploadista/server](../../servers/server/)** - Core server utilities
|
|
522
|
+
- **[@uploadista/kv-store-redis](../../kv-stores/redis/)** - Redis KV store
|
|
523
|
+
- **[@uploadista/core](../../core/)** - Core engine
|
|
524
|
+
|
|
525
|
+
## TypeScript Support
|
|
526
|
+
|
|
527
|
+
Full TypeScript support with comprehensive types:
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
import type { AzureStoreOptions } from "@uploadista/data-store-azure";
|
|
531
|
+
import { azureStore } from "@uploadista/data-store-azure";
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
## Troubleshooting
|
|
535
|
+
|
|
536
|
+
### Authentication Failed
|
|
537
|
+
|
|
538
|
+
```bash
|
|
539
|
+
# Check credentials
|
|
540
|
+
az account show
|
|
541
|
+
|
|
542
|
+
# Verify service principal has correct roles
|
|
543
|
+
az role assignment list \
|
|
544
|
+
--assignee your-client-id \
|
|
545
|
+
--scope /subscriptions/YOUR_SUBSCRIPTION_ID
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
### Container Not Found
|
|
549
|
+
|
|
550
|
+
```bash
|
|
551
|
+
# List containers
|
|
552
|
+
az storage container list \
|
|
553
|
+
--account-name myaccount
|
|
554
|
+
|
|
555
|
+
# Create if missing
|
|
556
|
+
az storage container create \
|
|
557
|
+
--name uploads \
|
|
558
|
+
--account-name myaccount
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
### Slow Uploads
|
|
562
|
+
|
|
563
|
+
- Reduce block size for network resilience
|
|
564
|
+
- Increase `maxConcurrentBlockUploads` for more parallelism
|
|
565
|
+
- Check network bandwidth
|
|
566
|
+
- Verify storage account replication setting
|
|
567
|
+
|
|
568
|
+
## License
|
|
569
|
+
|
|
570
|
+
MIT
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { TokenCredential } from "@azure/core-auth";
|
|
2
|
+
import { UploadistaError } from "@uploadista/core/errors";
|
|
3
|
+
import type { DataStore, KvStore, UploadFile } from "@uploadista/core/types";
|
|
4
|
+
import { Effect } from "effect";
|
|
5
|
+
export type ChunkInfo = {
|
|
6
|
+
blockNumber: number;
|
|
7
|
+
data: Uint8Array;
|
|
8
|
+
size: number;
|
|
9
|
+
isFinalPart?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export type AzureStoreOptions = {
|
|
12
|
+
deliveryUrl: string;
|
|
13
|
+
/**
|
|
14
|
+
* The preferred block size for blocks sent to Azure. Can not be lower than 1 byte or more than 4000MiB.
|
|
15
|
+
* The server calculates the optimal block size, which takes this size into account,
|
|
16
|
+
* but may increase it to not exceed the Azure 50K blocks limit.
|
|
17
|
+
*/
|
|
18
|
+
blockSize?: number;
|
|
19
|
+
/**
|
|
20
|
+
* The minimal block size for blocks.
|
|
21
|
+
* Can be used to ensure that all non-trailing blocks are exactly the same size.
|
|
22
|
+
* Can not be lower than 1 byte or more than 4000MiB.
|
|
23
|
+
*/
|
|
24
|
+
minBlockSize?: number;
|
|
25
|
+
/**
|
|
26
|
+
* The maximum number of blocks allowed in a block blob upload. Defaults to 50,000.
|
|
27
|
+
*/
|
|
28
|
+
maxBlocks?: number;
|
|
29
|
+
maxConcurrentBlockUploads?: number;
|
|
30
|
+
kvStore: KvStore<UploadFile>;
|
|
31
|
+
expirationPeriodInMilliseconds?: number;
|
|
32
|
+
connectionString?: string;
|
|
33
|
+
/**
|
|
34
|
+
* SAS URL for the storage account (works in all environments including browsers)
|
|
35
|
+
* Format: https://<account>.blob.core.windows.net?<sas-token>
|
|
36
|
+
*/
|
|
37
|
+
sasUrl?: string;
|
|
38
|
+
/**
|
|
39
|
+
* TokenCredential for OAuth authentication (e.g., DefaultAzureCredential)
|
|
40
|
+
* Works in all environments and is the recommended approach for production
|
|
41
|
+
*/
|
|
42
|
+
credential?: TokenCredential;
|
|
43
|
+
/**
|
|
44
|
+
* Account name and key for shared key authentication (Node.js only)
|
|
45
|
+
* @deprecated Use sasUrl or credential instead for cross-platform compatibility
|
|
46
|
+
*/
|
|
47
|
+
accountName?: string;
|
|
48
|
+
/**
|
|
49
|
+
* @deprecated Use sasUrl or credential instead for cross-platform compatibility
|
|
50
|
+
*/
|
|
51
|
+
accountKey?: string;
|
|
52
|
+
containerName: string;
|
|
53
|
+
};
|
|
54
|
+
export type AzureStore = DataStore<UploadFile> & {
|
|
55
|
+
getUpload: (id: string) => Effect.Effect<UploadFile, UploadistaError>;
|
|
56
|
+
readStream: (id: string) => Effect.Effect<ReadableStream | Blob, UploadistaError>;
|
|
57
|
+
getChunkerConstraints: () => {
|
|
58
|
+
minChunkSize: number;
|
|
59
|
+
maxChunkSize: number;
|
|
60
|
+
optimalChunkSize: number;
|
|
61
|
+
requiresOrderedChunks: boolean;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
export declare function azureStore({ deliveryUrl, blockSize, minBlockSize, // 1KB minimum
|
|
65
|
+
maxBlocks, kvStore, maxConcurrentBlockUploads, expirationPeriodInMilliseconds, // 1 week
|
|
66
|
+
connectionString, sasUrl, credential, accountName, accountKey, containerName, }: AzureStoreOptions): AzureStore;
|
|
67
|
+
//# sourceMappingURL=azure-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"azure-store.d.ts","sourceRoot":"","sources":["../src/azure-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAOxD,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAE1D,OAAO,KAAK,EACV,SAAS,EAGT,OAAO,EACP,UAAU,EAEX,MAAM,wBAAwB,CAAC;AAgBhC,OAAO,EAAE,MAAM,EAAe,MAAM,QAAQ,CAAC;AAY7C,MAAM,MAAM,SAAS,GAAG;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yBAAyB,CAAC,EAAE,MAAM,CAAC;IACnC,OAAO,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC7B,8BAA8B,CAAC,EAAE,MAAM,CAAC;IAExC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAQF,MAAM,MAAM,UAAU,GAAG,SAAS,CAAC,UAAU,CAAC,GAAG;IAC/C,SAAS,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC,MAAM,CAAC,UAAU,EAAE,eAAe,CAAC,CAAC;IACtE,UAAU,EAAE,CACV,EAAE,EAAE,MAAM,KACP,MAAM,CAAC,MAAM,CAAC,cAAc,GAAG,IAAI,EAAE,eAAe,CAAC,CAAC;IAC3D,qBAAqB,EAAE,MAAM;QAC3B,YAAY,EAAE,MAAM,CAAC;QACrB,YAAY,EAAE,MAAM,CAAC;QACrB,gBAAgB,EAAE,MAAM,CAAC;QACzB,qBAAqB,EAAE,OAAO,CAAC;KAChC,CAAC;CACH,CAAC;AAEF,wBAAgB,UAAU,CAAC,EACzB,WAAW,EACX,SAAS,EACT,YAAmB,EAAE,cAAc;AACnC,SAAkB,EAClB,OAAO,EACP,yBAA8B,EAC9B,8BAAwD,EAAE,SAAS;AACnE,gBAAgB,EAChB,MAAM,EACN,UAAU,EACV,WAAW,EACX,UAAU,EACV,aAAa,GACd,EAAE,iBAAiB,GAAG,UAAU,CAy7BhC"}
|