@zucker-framework/storage 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@zucker-framework/storage",
3
+ "version": "1.0.0",
4
+ "main": "dist/index.js",
5
+ "module": "dist/index.mjs",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist",
9
+ "scripts/r2-storage-canary.mjs",
10
+ "LICENSE",
11
+ "NOTICE",
12
+ "THIRD_PARTY_NOTICES"
13
+ ],
14
+ "dependencies": {
15
+ "@aws-sdk/client-s3": "^3.1124.0",
16
+ "@aws-sdk/s3-request-presigner": "^3.1124.0"
17
+ },
18
+ "devDependencies": {
19
+ "@types/node": "^20.19.43"
20
+ },
21
+ "peerDependencies": {
22
+ "@nestjs/common": "^12.0.0",
23
+ "@nestjs/core": "^12.0.0",
24
+ "webdav": "^5.0.0"
25
+ },
26
+ "license": "Apache-2.0",
27
+ "author": "Lind",
28
+ "repository": {
29
+ "type": "git",
30
+ "url": "git+https://github.com/Lind-pro/zucker-framework.git",
31
+ "directory": "packages/storage"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public",
35
+ "registry": "https://registry.npmjs.org/"
36
+ },
37
+ "scripts": {
38
+ "build": "tsup src/index.ts --format cjs,esm --dts --external @nestjs/common --external @nestjs/core --external webdav",
39
+ "canary:r2": "node scripts/r2-storage-canary.mjs",
40
+ "clean": "rm -rf dist",
41
+ "lint": "eslint src/",
42
+ "verify:package": "node scripts/verify-package.mjs"
43
+ }
44
+ }
@@ -0,0 +1,84 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import { S3Storage } from '../dist/index.mjs';
3
+
4
+ const required = ['R2_ENDPOINT', 'R2_ACCESS_KEY_ID', 'R2_SECRET_ACCESS_KEY', 'R2_BUCKET'];
5
+
6
+ if (process.env.R2_CANARY !== '1') {
7
+ throw new Error('Set R2_CANARY=1 to run the disposable R2 storage canary');
8
+ }
9
+ for (const key of required) {
10
+ if (!process.env[key]) throw new Error(`Missing required canary setting: ${key}`);
11
+ }
12
+
13
+ const storage = new S3Storage({
14
+ accessKeyId: process.env.R2_ACCESS_KEY_ID,
15
+ secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
16
+ endpoint: process.env.R2_ENDPOINT,
17
+ bucket: process.env.R2_BUCKET,
18
+ region: 'auto',
19
+ forcePathStyle: false,
20
+ objectAclMode: 'none',
21
+ uploadUrlMode: 'public-only',
22
+ });
23
+ const prefix = `_canary/storage/${randomUUID()}`;
24
+ const sourceKey = `${prefix}/source.txt`;
25
+ const copyKey = `${prefix}/copy.txt`;
26
+ const payload = Buffer.from(`zucker-storage-r2-canary:${randomUUID()}`);
27
+ const expectedHash = sha256(payload);
28
+ let verified = false;
29
+
30
+ try {
31
+ const uploaded = await storage.upload(payload, sourceKey, {
32
+ contentType: 'text/plain',
33
+ acl: 'private',
34
+ metadata: { sha256: expectedHash },
35
+ });
36
+ assert(uploaded.key === sourceKey && uploaded.url === '', 'private upload contract');
37
+ assert(await storage.exists(sourceKey), 'uploaded object existence');
38
+
39
+ const info = await storage.getFileInfo(sourceKey);
40
+ assert(info?.size === payload.length, 'head object size');
41
+
42
+ const downloaded = await read(await storage.download(sourceKey));
43
+ assert(downloaded.length === payload.length, 'download size');
44
+ assert(sha256(downloaded) === expectedHash, 'download hash');
45
+
46
+ const listed = await storage.list(prefix);
47
+ assert(
48
+ listed.some((file) => file.key === sourceKey),
49
+ 'list prefix',
50
+ );
51
+
52
+ await storage.copy(sourceKey, copyKey);
53
+ const copied = await read(await storage.download(copyKey));
54
+ assert(sha256(copied) === expectedHash, 'copy hash');
55
+
56
+ const signedUrl = await storage.getDownloadUrl(sourceKey, 300);
57
+ const response = await fetch(signedUrl);
58
+ assert(response.ok, 'signed URL response');
59
+ assert(sha256(Buffer.from(await response.arrayBuffer())) === expectedHash, 'signed URL hash');
60
+ verified = true;
61
+ } finally {
62
+ await Promise.allSettled([storage.delete(copyKey), storage.delete(sourceKey)]);
63
+ if (verified) {
64
+ assert(!(await storage.exists(sourceKey)), 'source delete');
65
+ assert(!(await storage.exists(copyKey)), 'copy delete');
66
+ }
67
+ storage.onModuleDestroy();
68
+ }
69
+
70
+ process.stdout.write('R2 canary passed: upload/head/download/list/copy/signed-url/delete\n');
71
+
72
+ function assert(condition, stage) {
73
+ if (!condition) throw new Error(`R2 canary failed at ${stage}`);
74
+ }
75
+
76
+ async function read(result) {
77
+ const chunks = [];
78
+ for await (const chunk of result.stream) chunks.push(Buffer.from(chunk));
79
+ return Buffer.concat(chunks);
80
+ }
81
+
82
+ function sha256(value) {
83
+ return createHash('sha256').update(value).digest('hex');
84
+ }