@wrelik/storage 0.2.1 → 2.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.
@@ -1,26 +1,27 @@
1
1
 
2
- > @wrelik/storage@0.2.1 build /home/runner/work/wrelik-kit/wrelik-kit/packages/storage
3
- > tsup src/index.ts src/react-native.ts --format cjs,esm --dts --clean
2
+ > @wrelik/storage@2.0.0 build /home/runner/work/wrelik-kit/wrelik-kit/packages/storage
3
+ > tsup src/server/index.ts src/client/index.ts src/shared/index.ts --format cjs,esm --dts --clean
4
4
 
5
- CLI Building entry: src/index.ts, src/react-native.ts
5
+ CLI Building entry: src/client/index.ts, src/server/index.ts, src/shared/index.ts
6
6
  CLI Using tsconfig: tsconfig.json
7
7
  CLI tsup v8.5.1
8
8
  CLI Target: es2022
9
9
  CLI Cleaning output folder
10
10
  CJS Build start
11
11
  ESM Build start
12
- ESM dist/index.mjs 1.46 KB
13
- ESM dist/react-native.mjs 701.00 B
14
- ESM dist/chunk-6VKCOC6I.mjs 421.00 B
15
- ESM ⚡️ Build success in 59ms
16
- CJS dist/index.js 3.10 KB
17
- CJS dist/react-native.js 2.17 KB
18
- CJS ⚡️ Build success in 61ms
12
+ CJS dist/server/index.js 4.13 KB
13
+ CJS dist/shared/index.js 1.44 KB
14
+ CJS dist/client/index.js 1.67 KB
15
+ CJS ⚡️ Build success in 82ms
16
+ ESM dist/client/index.mjs 625.00 B
17
+ ESM dist/server/index.mjs 2.86 KB
18
+ ESM dist/shared/index.mjs 433.00 B
19
+ ESM ⚡️ Build success in 82ms
19
20
  DTS Build start
20
- DTS ⚡️ Build success in 4085ms
21
- DTS dist/index.d.ts 815.00 B
22
- DTS dist/react-native.d.ts 457.00 B
23
- DTS dist/shared-DqbBUqiW.d.ts 201.00 B
24
- DTS dist/index.d.mts 816.00 B
25
- DTS dist/react-native.d.mts 458.00 B
26
- DTS dist/shared-DqbBUqiW.d.mts 201.00 B
21
+ DTS ⚡️ Build success in 6479ms
22
+ DTS dist/client/index.d.ts 337.00 B
23
+ DTS dist/server/index.d.ts 1.99 KB
24
+ DTS dist/shared/index.d.ts 193.00 B
25
+ DTS dist/client/index.d.mts 337.00 B
26
+ DTS dist/server/index.d.mts 1.99 KB
27
+ DTS dist/shared/index.d.mts 193.00 B
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @wrelik/storage
2
2
 
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 818ed58: Refactor runtime packages to strict subpath exports (`/server`, `/client`, `/shared`) with side-effect free entrypoints and hard CI/runtime boundary enforcement.
8
+
9
+ ### Minor Changes
10
+
11
+ - Add platform wrapper packages for Clerk (Next/Expo), Next Sentry client instrumentation, and Upstash server SDK access so apps can avoid direct vendor imports while staying inside the `@wrelik/*` boundary.
12
+
13
+ Add temporary deprecated server-side compatibility singleton exports to analytics, email, jobs, and storage to support DRX migration from root-import convenience APIs to runtime subpaths in one cutover.
14
+
15
+ ### Patch Changes
16
+
17
+ - Updated dependencies [818ed58]
18
+ - @wrelik/errors@2.0.0
19
+
3
20
  ## 0.2.1
4
21
 
5
22
  ### Patch Changes
@@ -1,14 +1,11 @@
1
- import * as node_buffer from 'node:buffer';
2
- export { v as validateUpload } from './shared-DqbBUqiW.js';
3
-
4
1
  declare function uploadToSignedUrl({ url, file, contentType, headers, }: {
5
2
  url: string;
6
3
  file: File | Blob;
7
4
  contentType: string;
8
5
  headers?: Record<string, string>;
9
- }): Promise<boolean>;
6
+ }): Promise<true>;
10
7
  declare function downloadFromSignedUrl({ url }: {
11
8
  url: string;
12
- }): Promise<node_buffer.Blob>;
9
+ }): Promise<Blob>;
13
10
 
14
11
  export { downloadFromSignedUrl, uploadToSignedUrl };
@@ -1,14 +1,11 @@
1
- import * as node_buffer from 'node:buffer';
2
- export { v as validateUpload } from './shared-DqbBUqiW.mjs';
3
-
4
1
  declare function uploadToSignedUrl({ url, file, contentType, headers, }: {
5
2
  url: string;
6
3
  file: File | Blob;
7
4
  contentType: string;
8
5
  headers?: Record<string, string>;
9
- }): Promise<boolean>;
6
+ }): Promise<true>;
10
7
  declare function downloadFromSignedUrl({ url }: {
11
8
  url: string;
12
- }): Promise<node_buffer.Blob>;
9
+ }): Promise<Blob>;
13
10
 
14
11
  export { downloadFromSignedUrl, uploadToSignedUrl };
@@ -17,28 +17,13 @@ var __copyProps = (to, from, except, desc) => {
17
17
  };
18
18
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
19
 
20
- // src/react-native.ts
21
- var react_native_exports = {};
22
- __export(react_native_exports, {
20
+ // src/client/index.ts
21
+ var client_exports = {};
22
+ __export(client_exports, {
23
23
  downloadFromSignedUrl: () => downloadFromSignedUrl,
24
- uploadToSignedUrl: () => uploadToSignedUrl,
25
- validateUpload: () => validateUpload
24
+ uploadToSignedUrl: () => uploadToSignedUrl
26
25
  });
27
- module.exports = __toCommonJS(react_native_exports);
28
-
29
- // src/shared.ts
30
- var import_errors = require("@wrelik/errors");
31
- function validateUpload(file, policy) {
32
- if (file.sizeBytes > policy.maxSizeBytes) {
33
- throw new import_errors.ValidationError(`File too large. Max ${policy.maxSizeBytes} bytes.`);
34
- }
35
- if (!policy.allowedTypes.includes(file.contentType)) {
36
- throw new import_errors.ValidationError(`Invalid file type ${file.contentType}`);
37
- }
38
- return true;
39
- }
40
-
41
- // src/react-native.ts
26
+ module.exports = __toCommonJS(client_exports);
42
27
  async function uploadToSignedUrl({
43
28
  url,
44
29
  file,
@@ -68,6 +53,5 @@ async function downloadFromSignedUrl({ url }) {
68
53
  // Annotate the CommonJS export names for ESM import in node:
69
54
  0 && (module.exports = {
70
55
  downloadFromSignedUrl,
71
- uploadToSignedUrl,
72
- validateUpload
56
+ uploadToSignedUrl
73
57
  });
@@ -1,8 +1,4 @@
1
- import {
2
- validateUpload
3
- } from "./chunk-6VKCOC6I.mjs";
4
-
5
- // src/react-native.ts
1
+ // src/client/index.ts
6
2
  async function uploadToSignedUrl({
7
3
  url,
8
4
  file,
@@ -31,6 +27,5 @@ async function downloadFromSignedUrl({ url }) {
31
27
  }
32
28
  export {
33
29
  downloadFromSignedUrl,
34
- uploadToSignedUrl,
35
- validateUpload
30
+ uploadToSignedUrl
36
31
  };
@@ -0,0 +1,39 @@
1
+ interface StorageConfig {
2
+ accountId: string;
3
+ accessKeyId: string;
4
+ secretAccessKey: string;
5
+ bucketName: string;
6
+ region?: string;
7
+ }
8
+ interface StorageServer {
9
+ putObject: (body: Buffer | Uint8Array | Blob | string, key: string, contentType: string, metadata?: Record<string, string>) => Promise<unknown>;
10
+ getSignedUploadUrl: (key: string, contentType: string, expiresIn?: number) => Promise<string>;
11
+ getSignedDownloadUrl: (key: string, expiresIn?: number) => Promise<string>;
12
+ }
13
+ interface StorageServerDependencies {
14
+ client?: {
15
+ send: (command: unknown) => Promise<unknown>;
16
+ };
17
+ signUrl?: (client: unknown, command: unknown, options: {
18
+ expiresIn: number;
19
+ }) => Promise<string>;
20
+ }
21
+ declare function createStorageServer(config: StorageConfig, deps?: StorageServerDependencies): StorageServer;
22
+ /**
23
+ * @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...) and explicit dependency wiring.
24
+ */
25
+ declare function initStorage(config: StorageConfig, deps?: StorageServerDependencies): void;
26
+ /**
27
+ * @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).putObject(...)
28
+ */
29
+ declare function putObject(body: Parameters<StorageServer['putObject']>[0], key: string, contentType: string, metadata?: Record<string, string>): ReturnType<StorageServer['putObject']>;
30
+ /**
31
+ * @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).getSignedUploadUrl(...)
32
+ */
33
+ declare function getSignedUploadUrl(key: string, contentType: string, expiresIn?: number): ReturnType<StorageServer['getSignedUploadUrl']>;
34
+ /**
35
+ * @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).getSignedDownloadUrl(...)
36
+ */
37
+ declare function getSignedDownloadUrl(key: string, expiresIn?: number): ReturnType<StorageServer['getSignedDownloadUrl']>;
38
+
39
+ export { type StorageConfig, type StorageServer, type StorageServerDependencies, createStorageServer, getSignedDownloadUrl, getSignedUploadUrl, initStorage, putObject };
@@ -0,0 +1,39 @@
1
+ interface StorageConfig {
2
+ accountId: string;
3
+ accessKeyId: string;
4
+ secretAccessKey: string;
5
+ bucketName: string;
6
+ region?: string;
7
+ }
8
+ interface StorageServer {
9
+ putObject: (body: Buffer | Uint8Array | Blob | string, key: string, contentType: string, metadata?: Record<string, string>) => Promise<unknown>;
10
+ getSignedUploadUrl: (key: string, contentType: string, expiresIn?: number) => Promise<string>;
11
+ getSignedDownloadUrl: (key: string, expiresIn?: number) => Promise<string>;
12
+ }
13
+ interface StorageServerDependencies {
14
+ client?: {
15
+ send: (command: unknown) => Promise<unknown>;
16
+ };
17
+ signUrl?: (client: unknown, command: unknown, options: {
18
+ expiresIn: number;
19
+ }) => Promise<string>;
20
+ }
21
+ declare function createStorageServer(config: StorageConfig, deps?: StorageServerDependencies): StorageServer;
22
+ /**
23
+ * @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...) and explicit dependency wiring.
24
+ */
25
+ declare function initStorage(config: StorageConfig, deps?: StorageServerDependencies): void;
26
+ /**
27
+ * @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).putObject(...)
28
+ */
29
+ declare function putObject(body: Parameters<StorageServer['putObject']>[0], key: string, contentType: string, metadata?: Record<string, string>): ReturnType<StorageServer['putObject']>;
30
+ /**
31
+ * @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).getSignedUploadUrl(...)
32
+ */
33
+ declare function getSignedUploadUrl(key: string, contentType: string, expiresIn?: number): ReturnType<StorageServer['getSignedUploadUrl']>;
34
+ /**
35
+ * @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).getSignedDownloadUrl(...)
36
+ */
37
+ declare function getSignedDownloadUrl(key: string, expiresIn?: number): ReturnType<StorageServer['getSignedDownloadUrl']>;
38
+
39
+ export { type StorageConfig, type StorageServer, type StorageServerDependencies, createStorageServer, getSignedDownloadUrl, getSignedUploadUrl, initStorage, putObject };
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/server/index.ts
21
+ var server_exports = {};
22
+ __export(server_exports, {
23
+ createStorageServer: () => createStorageServer,
24
+ getSignedDownloadUrl: () => getSignedDownloadUrl,
25
+ getSignedUploadUrl: () => getSignedUploadUrl,
26
+ initStorage: () => initStorage,
27
+ putObject: () => putObject
28
+ });
29
+ module.exports = __toCommonJS(server_exports);
30
+ var import_client_s3 = require("@aws-sdk/client-s3");
31
+ var import_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
32
+ var import_shared = require("@wrelik/errors/shared");
33
+ function assertNonEmpty(value, field) {
34
+ if (!value?.trim()) {
35
+ throw new import_shared.ValidationError(`${field} is required`);
36
+ }
37
+ }
38
+ function assertExpiresIn(expiresIn) {
39
+ if (!Number.isInteger(expiresIn) || expiresIn <= 0) {
40
+ throw new import_shared.ValidationError("expiresIn must be a positive integer");
41
+ }
42
+ }
43
+ function createStorageServer(config, deps) {
44
+ const endpoint = `https://${config.accountId}.r2.cloudflarestorage.com`;
45
+ const client = deps?.client ?? new import_client_s3.S3Client({
46
+ region: config.region ?? "auto",
47
+ endpoint,
48
+ credentials: {
49
+ accessKeyId: config.accessKeyId,
50
+ secretAccessKey: config.secretAccessKey
51
+ }
52
+ });
53
+ const signUrl = deps?.signUrl ?? ((client2, command, options) => (0, import_s3_request_presigner.getSignedUrl)(client2, command, options));
54
+ return {
55
+ putObject(body, key, contentType, metadata) {
56
+ assertNonEmpty(key, "key");
57
+ assertNonEmpty(contentType, "contentType");
58
+ return client.send(
59
+ new import_client_s3.PutObjectCommand({
60
+ Bucket: config.bucketName,
61
+ Key: key,
62
+ Body: body,
63
+ ContentType: contentType,
64
+ Metadata: metadata
65
+ })
66
+ );
67
+ },
68
+ getSignedUploadUrl(key, contentType, expiresIn = 3600) {
69
+ assertNonEmpty(key, "key");
70
+ assertNonEmpty(contentType, "contentType");
71
+ assertExpiresIn(expiresIn);
72
+ const command = new import_client_s3.PutObjectCommand({
73
+ Bucket: config.bucketName,
74
+ Key: key,
75
+ ContentType: contentType
76
+ });
77
+ return signUrl(client, command, { expiresIn });
78
+ },
79
+ getSignedDownloadUrl(key, expiresIn = 3600) {
80
+ assertNonEmpty(key, "key");
81
+ assertExpiresIn(expiresIn);
82
+ const command = new import_client_s3.GetObjectCommand({
83
+ Bucket: config.bucketName,
84
+ Key: key
85
+ });
86
+ return signUrl(client, command, { expiresIn });
87
+ }
88
+ };
89
+ }
90
+ var storageSingleton = null;
91
+ function requireStorageSingleton() {
92
+ if (!storageSingleton) {
93
+ throw new Error(
94
+ "@wrelik/storage/server compatibility API is not initialized. Call initStorage(config) before putObject/getSignedUploadUrl/getSignedDownloadUrl."
95
+ );
96
+ }
97
+ return storageSingleton;
98
+ }
99
+ function initStorage(config, deps) {
100
+ storageSingleton = createStorageServer(config, deps);
101
+ }
102
+ function putObject(body, key, contentType, metadata) {
103
+ return requireStorageSingleton().putObject(body, key, contentType, metadata);
104
+ }
105
+ function getSignedUploadUrl(key, contentType, expiresIn) {
106
+ return requireStorageSingleton().getSignedUploadUrl(key, contentType, expiresIn);
107
+ }
108
+ function getSignedDownloadUrl(key, expiresIn) {
109
+ return requireStorageSingleton().getSignedDownloadUrl(key, expiresIn);
110
+ }
111
+ // Annotate the CommonJS export names for ESM import in node:
112
+ 0 && (module.exports = {
113
+ createStorageServer,
114
+ getSignedDownloadUrl,
115
+ getSignedUploadUrl,
116
+ initStorage,
117
+ putObject
118
+ });
@@ -0,0 +1,89 @@
1
+ // src/server/index.ts
2
+ import { GetObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
3
+ import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
4
+ import { ValidationError } from "@wrelik/errors/shared";
5
+ function assertNonEmpty(value, field) {
6
+ if (!value?.trim()) {
7
+ throw new ValidationError(`${field} is required`);
8
+ }
9
+ }
10
+ function assertExpiresIn(expiresIn) {
11
+ if (!Number.isInteger(expiresIn) || expiresIn <= 0) {
12
+ throw new ValidationError("expiresIn must be a positive integer");
13
+ }
14
+ }
15
+ function createStorageServer(config, deps) {
16
+ const endpoint = `https://${config.accountId}.r2.cloudflarestorage.com`;
17
+ const client = deps?.client ?? new S3Client({
18
+ region: config.region ?? "auto",
19
+ endpoint,
20
+ credentials: {
21
+ accessKeyId: config.accessKeyId,
22
+ secretAccessKey: config.secretAccessKey
23
+ }
24
+ });
25
+ const signUrl = deps?.signUrl ?? ((client2, command, options) => getSignedUrl(client2, command, options));
26
+ return {
27
+ putObject(body, key, contentType, metadata) {
28
+ assertNonEmpty(key, "key");
29
+ assertNonEmpty(contentType, "contentType");
30
+ return client.send(
31
+ new PutObjectCommand({
32
+ Bucket: config.bucketName,
33
+ Key: key,
34
+ Body: body,
35
+ ContentType: contentType,
36
+ Metadata: metadata
37
+ })
38
+ );
39
+ },
40
+ getSignedUploadUrl(key, contentType, expiresIn = 3600) {
41
+ assertNonEmpty(key, "key");
42
+ assertNonEmpty(contentType, "contentType");
43
+ assertExpiresIn(expiresIn);
44
+ const command = new PutObjectCommand({
45
+ Bucket: config.bucketName,
46
+ Key: key,
47
+ ContentType: contentType
48
+ });
49
+ return signUrl(client, command, { expiresIn });
50
+ },
51
+ getSignedDownloadUrl(key, expiresIn = 3600) {
52
+ assertNonEmpty(key, "key");
53
+ assertExpiresIn(expiresIn);
54
+ const command = new GetObjectCommand({
55
+ Bucket: config.bucketName,
56
+ Key: key
57
+ });
58
+ return signUrl(client, command, { expiresIn });
59
+ }
60
+ };
61
+ }
62
+ var storageSingleton = null;
63
+ function requireStorageSingleton() {
64
+ if (!storageSingleton) {
65
+ throw new Error(
66
+ "@wrelik/storage/server compatibility API is not initialized. Call initStorage(config) before putObject/getSignedUploadUrl/getSignedDownloadUrl."
67
+ );
68
+ }
69
+ return storageSingleton;
70
+ }
71
+ function initStorage(config, deps) {
72
+ storageSingleton = createStorageServer(config, deps);
73
+ }
74
+ function putObject(body, key, contentType, metadata) {
75
+ return requireStorageSingleton().putObject(body, key, contentType, metadata);
76
+ }
77
+ function getSignedUploadUrl(key, contentType, expiresIn) {
78
+ return requireStorageSingleton().getSignedUploadUrl(key, contentType, expiresIn);
79
+ }
80
+ function getSignedDownloadUrl(key, expiresIn) {
81
+ return requireStorageSingleton().getSignedDownloadUrl(key, expiresIn);
82
+ }
83
+ export {
84
+ createStorageServer,
85
+ getSignedDownloadUrl,
86
+ getSignedUploadUrl,
87
+ initStorage,
88
+ putObject
89
+ };
@@ -4,6 +4,6 @@ declare function validateUpload(file: {
4
4
  }, policy: {
5
5
  maxSizeBytes: number;
6
6
  allowedTypes: string[];
7
- }): boolean;
7
+ }): true;
8
8
 
9
- export { validateUpload as v };
9
+ export { validateUpload };
@@ -4,6 +4,6 @@ declare function validateUpload(file: {
4
4
  }, policy: {
5
5
  maxSizeBytes: number;
6
6
  allowedTypes: string[];
7
- }): boolean;
7
+ }): true;
8
8
 
9
- export { validateUpload as v };
9
+ export { validateUpload };
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/shared/index.ts
21
+ var shared_exports = {};
22
+ __export(shared_exports, {
23
+ validateUpload: () => validateUpload
24
+ });
25
+ module.exports = __toCommonJS(shared_exports);
26
+ var import_shared = require("@wrelik/errors/shared");
27
+ function validateUpload(file, policy) {
28
+ if (file.sizeBytes > policy.maxSizeBytes) {
29
+ throw new import_shared.ValidationError(`File too large. Max ${policy.maxSizeBytes} bytes.`);
30
+ }
31
+ if (!policy.allowedTypes.includes(file.contentType)) {
32
+ throw new import_shared.ValidationError(`Invalid file type ${file.contentType}`);
33
+ }
34
+ return true;
35
+ }
36
+ // Annotate the CommonJS export names for ESM import in node:
37
+ 0 && (module.exports = {
38
+ validateUpload
39
+ });
@@ -1,5 +1,5 @@
1
- // src/shared.ts
2
- import { ValidationError } from "@wrelik/errors";
1
+ // src/shared/index.ts
2
+ import { ValidationError } from "@wrelik/errors/shared";
3
3
  function validateUpload(file, policy) {
4
4
  if (file.sizeBytes > policy.maxSizeBytes) {
5
5
  throw new ValidationError(`File too large. Max ${policy.maxSizeBytes} bytes.`);
@@ -9,7 +9,6 @@ function validateUpload(file, policy) {
9
9
  }
10
10
  return true;
11
11
  }
12
-
13
12
  export {
14
13
  validateUpload
15
14
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrelik/storage",
3
- "version": "0.2.1",
3
+ "version": "2.0.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -9,25 +9,48 @@
9
9
  "url": "https://github.com/lwhite702/wrelik-kit.git",
10
10
  "directory": "packages/storage"
11
11
  },
12
+ "sideEffects": false,
12
13
  "exports": {
13
- ".": "./dist/index.js",
14
- "./react-native": "./dist/react-native.js"
14
+ "./server": {
15
+ "types": "./dist/server/index.d.ts",
16
+ "import": "./dist/server/index.mjs",
17
+ "require": "./dist/server/index.js"
18
+ },
19
+ "./client": {
20
+ "types": "./dist/client/index.d.ts",
21
+ "import": "./dist/client/index.mjs",
22
+ "require": "./dist/client/index.js"
23
+ },
24
+ "./shared": {
25
+ "types": "./dist/shared/index.d.ts",
26
+ "import": "./dist/shared/index.mjs",
27
+ "require": "./dist/shared/index.js"
28
+ },
29
+ "./package.json": "./package.json"
15
30
  },
16
31
  "dependencies": {
17
32
  "@aws-sdk/client-s3": "^3.500.0",
18
33
  "@aws-sdk/s3-request-presigner": "^3.500.0",
19
- "@wrelik/errors": "0.2.1"
34
+ "@wrelik/errors": "2.0.0"
20
35
  },
21
36
  "devDependencies": {
22
37
  "@types/node": "^25.2.0",
23
38
  "tsup": "^8.0.1",
24
39
  "vitest": "^1.2.2",
25
- "@wrelik/eslint-config": "0.1.1",
26
- "@wrelik/tsconfig": "0.1.1"
40
+ "@wrelik/tsconfig": "0.1.1",
41
+ "@wrelik/eslint-config": "0.1.1"
42
+ },
43
+ "wrelik": {
44
+ "runtimes": [
45
+ "server",
46
+ "client",
47
+ "shared"
48
+ ]
27
49
  },
28
50
  "scripts": {
29
- "build": "tsup src/index.ts src/react-native.ts --format cjs,esm --dts --clean",
51
+ "build": "tsup src/server/index.ts src/client/index.ts src/shared/index.ts --format cjs,esm --dts --clean",
30
52
  "lint": "eslint src/",
53
+ "typecheck": "tsc --noEmit",
31
54
  "test": "vitest run --passWithNoTests"
32
55
  }
33
56
  }
@@ -1,5 +1,3 @@
1
- export * from './shared';
2
-
3
1
  export async function uploadToSignedUrl({
4
2
  url,
5
3
  file,
@@ -10,7 +8,7 @@ export async function uploadToSignedUrl({
10
8
  file: File | Blob;
11
9
  contentType: string;
12
10
  headers?: Record<string, string>;
13
- }) {
11
+ }): Promise<true> {
14
12
  const result = await fetch(url, {
15
13
  method: 'PUT',
16
14
  body: file,
@@ -27,10 +25,11 @@ export async function uploadToSignedUrl({
27
25
  return true;
28
26
  }
29
27
 
30
- export async function downloadFromSignedUrl({ url }: { url: string }) {
28
+ export async function downloadFromSignedUrl({ url }: { url: string }): Promise<Blob> {
31
29
  const result = await fetch(url);
32
30
  if (!result.ok) {
33
31
  throw new Error(`Download failed with status ${result.status}`);
34
32
  }
33
+
35
34
  return result.blob();
36
35
  }
@@ -0,0 +1,71 @@
1
+ import { afterEach, describe, expect, it, vi } from 'vitest';
2
+ import { ValidationError } from '@wrelik/errors/shared';
3
+ import { createStorageServer } from './index';
4
+
5
+ const config = {
6
+ accountId: 'acct',
7
+ accessKeyId: 'key',
8
+ secretAccessKey: 'secret',
9
+ bucketName: 'bucket',
10
+ };
11
+
12
+ afterEach(() => {
13
+ vi.resetModules();
14
+ vi.clearAllMocks();
15
+ });
16
+
17
+ describe('@wrelik/storage/server', () => {
18
+ it('generates signed URL shapes with configured signer', async () => {
19
+ const signUrl = vi.fn(async (_client, command, options: { expiresIn: number }) => {
20
+ const input = (command as { input: { Key: string } }).input;
21
+ return `https://signed.example/${input.Key}?expiresIn=${options.expiresIn}`;
22
+ });
23
+
24
+ const storage = createStorageServer(config, {
25
+ client: { send: vi.fn() },
26
+ signUrl,
27
+ });
28
+
29
+ await expect(storage.getSignedUploadUrl('uploads/a.png', 'image/png', 60)).resolves.toBe(
30
+ 'https://signed.example/uploads/a.png?expiresIn=60',
31
+ );
32
+ await expect(storage.getSignedDownloadUrl('uploads/a.png', 120)).resolves.toBe(
33
+ 'https://signed.example/uploads/a.png?expiresIn=120',
34
+ );
35
+ });
36
+
37
+ it('validates signed URL input rules', async () => {
38
+ const storage = createStorageServer(config, {
39
+ client: { send: vi.fn() },
40
+ signUrl: vi.fn(async () => 'https://signed.example/ok'),
41
+ });
42
+
43
+ expect(() => storage.getSignedUploadUrl('', 'image/png')).toThrow(ValidationError);
44
+ expect(() => storage.getSignedUploadUrl('a.png', '', 60)).toThrow(ValidationError);
45
+ expect(() => storage.getSignedDownloadUrl('a.png', 0)).toThrow(/positive integer/);
46
+ });
47
+
48
+ it('compatibility singleton throws before initStorage', async () => {
49
+ const mod = await import('./index.js');
50
+ expect(() => mod.getSignedDownloadUrl('uploads/a.png', 60)).toThrow(/initStorage/i);
51
+ });
52
+
53
+ it('compatibility singleton delegates after initStorage', async () => {
54
+ const send = vi.fn(async () => ({ etag: 'etag_1' }));
55
+ const signUrl = vi.fn(async (_client, command, options: { expiresIn: number }) => {
56
+ const input = (command as { input: { Key: string } }).input;
57
+ return `https://signed.example/${input.Key}?expiresIn=${options.expiresIn}`;
58
+ });
59
+ const mod = await import('./index.js');
60
+
61
+ mod.initStorage(config, { client: { send }, signUrl });
62
+
63
+ await mod.putObject('body', 'uploads/a.png', 'image/png');
64
+ await expect(mod.getSignedUploadUrl('uploads/a.png', 'image/png', 30)).resolves.toBe(
65
+ 'https://signed.example/uploads/a.png?expiresIn=30',
66
+ );
67
+
68
+ expect(send).toHaveBeenCalledTimes(1);
69
+ expect(signUrl).toHaveBeenCalledTimes(1);
70
+ });
71
+ });
@@ -0,0 +1,147 @@
1
+ /* eslint-disable no-restricted-imports */
2
+ import { GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
3
+ import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
4
+ import { ValidationError } from '@wrelik/errors/shared';
5
+
6
+ export interface StorageConfig {
7
+ accountId: string;
8
+ accessKeyId: string;
9
+ secretAccessKey: string;
10
+ bucketName: string;
11
+ region?: string;
12
+ }
13
+
14
+ export interface StorageServer {
15
+ putObject: (
16
+ body: Buffer | Uint8Array | Blob | string,
17
+ key: string,
18
+ contentType: string,
19
+ metadata?: Record<string, string>,
20
+ ) => Promise<unknown>;
21
+ getSignedUploadUrl: (key: string, contentType: string, expiresIn?: number) => Promise<string>;
22
+ getSignedDownloadUrl: (key: string, expiresIn?: number) => Promise<string>;
23
+ }
24
+
25
+ export interface StorageServerDependencies {
26
+ client?: {
27
+ send: (command: unknown) => Promise<unknown>;
28
+ };
29
+ signUrl?: (client: unknown, command: unknown, options: { expiresIn: number }) => Promise<string>;
30
+ }
31
+
32
+ function assertNonEmpty(value: string, field: string): void {
33
+ if (!value?.trim()) {
34
+ throw new ValidationError(`${field} is required`);
35
+ }
36
+ }
37
+
38
+ function assertExpiresIn(expiresIn: number): void {
39
+ if (!Number.isInteger(expiresIn) || expiresIn <= 0) {
40
+ throw new ValidationError('expiresIn must be a positive integer');
41
+ }
42
+ }
43
+
44
+ export function createStorageServer(config: StorageConfig, deps?: StorageServerDependencies): StorageServer {
45
+ const endpoint = `https://${config.accountId}.r2.cloudflarestorage.com`;
46
+ const client =
47
+ deps?.client ??
48
+ new S3Client({
49
+ region: config.region ?? 'auto',
50
+ endpoint,
51
+ credentials: {
52
+ accessKeyId: config.accessKeyId,
53
+ secretAccessKey: config.secretAccessKey,
54
+ },
55
+ });
56
+ const signUrl =
57
+ deps?.signUrl ??
58
+ ((client: unknown, command: unknown, options: { expiresIn: number }) =>
59
+ getSignedUrl(client as S3Client, command as PutObjectCommand | GetObjectCommand, options));
60
+
61
+ return {
62
+ putObject(body, key, contentType, metadata) {
63
+ assertNonEmpty(key, 'key');
64
+ assertNonEmpty(contentType, 'contentType');
65
+ return client.send(
66
+ new PutObjectCommand({
67
+ Bucket: config.bucketName,
68
+ Key: key,
69
+ Body: body,
70
+ ContentType: contentType,
71
+ Metadata: metadata,
72
+ }),
73
+ );
74
+ },
75
+
76
+ getSignedUploadUrl(key, contentType, expiresIn = 3600) {
77
+ assertNonEmpty(key, 'key');
78
+ assertNonEmpty(contentType, 'contentType');
79
+ assertExpiresIn(expiresIn);
80
+ const command = new PutObjectCommand({
81
+ Bucket: config.bucketName,
82
+ Key: key,
83
+ ContentType: contentType,
84
+ });
85
+ return signUrl(client, command, { expiresIn });
86
+ },
87
+
88
+ getSignedDownloadUrl(key, expiresIn = 3600) {
89
+ assertNonEmpty(key, 'key');
90
+ assertExpiresIn(expiresIn);
91
+ const command = new GetObjectCommand({
92
+ Bucket: config.bucketName,
93
+ Key: key,
94
+ });
95
+ return signUrl(client, command, { expiresIn });
96
+ },
97
+ };
98
+ }
99
+
100
+ let storageSingleton: StorageServer | null = null;
101
+
102
+ function requireStorageSingleton(): StorageServer {
103
+ if (!storageSingleton) {
104
+ throw new Error(
105
+ '@wrelik/storage/server compatibility API is not initialized. Call initStorage(config) before putObject/getSignedUploadUrl/getSignedDownloadUrl.',
106
+ );
107
+ }
108
+
109
+ return storageSingleton;
110
+ }
111
+
112
+ /**
113
+ * @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...) and explicit dependency wiring.
114
+ */
115
+ export function initStorage(config: StorageConfig, deps?: StorageServerDependencies): void {
116
+ storageSingleton = createStorageServer(config, deps);
117
+ }
118
+
119
+ /**
120
+ * @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).putObject(...)
121
+ */
122
+ export function putObject(
123
+ body: Parameters<StorageServer['putObject']>[0],
124
+ key: string,
125
+ contentType: string,
126
+ metadata?: Record<string, string>,
127
+ ): ReturnType<StorageServer['putObject']> {
128
+ return requireStorageSingleton().putObject(body, key, contentType, metadata);
129
+ }
130
+
131
+ /**
132
+ * @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).getSignedUploadUrl(...)
133
+ */
134
+ export function getSignedUploadUrl(
135
+ key: string,
136
+ contentType: string,
137
+ expiresIn?: number,
138
+ ): ReturnType<StorageServer['getSignedUploadUrl']> {
139
+ return requireStorageSingleton().getSignedUploadUrl(key, contentType, expiresIn);
140
+ }
141
+
142
+ /**
143
+ * @deprecated Temporary DRX compatibility singleton. Prefer createStorageServer(...).getSignedDownloadUrl(...)
144
+ */
145
+ export function getSignedDownloadUrl(key: string, expiresIn?: number): ReturnType<StorageServer['getSignedDownloadUrl']> {
146
+ return requireStorageSingleton().getSignedDownloadUrl(key, expiresIn);
147
+ }
@@ -1,12 +1,11 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { validateUpload } from './shared';
1
+ import { describe, expect, it } from 'vitest';
2
+ import { validateUpload } from './index';
3
3
 
4
- describe('Storage Shared', () => {
4
+ describe('@wrelik/storage/shared', () => {
5
5
  it('validates upload policy', () => {
6
6
  const policy = { maxSizeBytes: 100, allowedTypes: ['image/png'] };
7
7
 
8
8
  expect(() => validateUpload({ contentType: 'image/png', sizeBytes: 50 }, policy)).not.toThrow();
9
-
10
9
  expect(() => validateUpload({ contentType: 'image/jpeg', sizeBytes: 50 }, policy)).toThrow(
11
10
  /Invalid file type/,
12
11
  );
@@ -1,14 +1,16 @@
1
- import { ValidationError } from '@wrelik/errors';
1
+ import { ValidationError } from '@wrelik/errors/shared';
2
2
 
3
3
  export function validateUpload(
4
4
  file: { contentType: string; sizeBytes: number },
5
5
  policy: { maxSizeBytes: number; allowedTypes: string[] },
6
- ) {
6
+ ): true {
7
7
  if (file.sizeBytes > policy.maxSizeBytes) {
8
8
  throw new ValidationError(`File too large. Max ${policy.maxSizeBytes} bytes.`);
9
9
  }
10
+
10
11
  if (!policy.allowedTypes.includes(file.contentType)) {
11
12
  throw new ValidationError(`Invalid file type ${file.contentType}`);
12
13
  }
14
+
13
15
  return true;
14
16
  }
package/tsconfig.json CHANGED
@@ -1,7 +1,9 @@
1
1
  {
2
2
  "extends": "@wrelik/tsconfig/node.json",
3
3
  "compilerOptions": {
4
- "outDir": "dist", "skipLibCheck": true
4
+ "outDir": "dist",
5
+ "skipLibCheck": true,
6
+ "lib": ["DOM", "DOM.Iterable", "ES2022"]
5
7
  },
6
8
  "include": ["src"]
7
9
  }
package/dist/index.d.mts DELETED
@@ -1,16 +0,0 @@
1
- import * as _aws_sdk_client_s3 from '@aws-sdk/client-s3';
2
- export { v as validateUpload } from './shared-DqbBUqiW.mjs';
3
-
4
- interface StorageConfig {
5
- accountId: string;
6
- accessKeyId: string;
7
- secretAccessKey: string;
8
- bucketName: string;
9
- region?: string;
10
- }
11
- declare function initStorage(config: StorageConfig): void;
12
- declare function putObject(body: Buffer | Uint8Array | Blob | string, key: string, contentType: string, metadata?: Record<string, string>): Promise<_aws_sdk_client_s3.PutObjectCommandOutput>;
13
- declare function getSignedUploadUrl(key: string, contentType: string, expiresIn?: number): Promise<string>;
14
- declare function getSignedDownloadUrl(key: string, expiresIn?: number): Promise<string>;
15
-
16
- export { type StorageConfig, getSignedDownloadUrl, getSignedUploadUrl, initStorage, putObject };
package/dist/index.d.ts DELETED
@@ -1,16 +0,0 @@
1
- import * as _aws_sdk_client_s3 from '@aws-sdk/client-s3';
2
- export { v as validateUpload } from './shared-DqbBUqiW.js';
3
-
4
- interface StorageConfig {
5
- accountId: string;
6
- accessKeyId: string;
7
- secretAccessKey: string;
8
- bucketName: string;
9
- region?: string;
10
- }
11
- declare function initStorage(config: StorageConfig): void;
12
- declare function putObject(body: Buffer | Uint8Array | Blob | string, key: string, contentType: string, metadata?: Record<string, string>): Promise<_aws_sdk_client_s3.PutObjectCommandOutput>;
13
- declare function getSignedUploadUrl(key: string, contentType: string, expiresIn?: number): Promise<string>;
14
- declare function getSignedDownloadUrl(key: string, expiresIn?: number): Promise<string>;
15
-
16
- export { type StorageConfig, getSignedDownloadUrl, getSignedUploadUrl, initStorage, putObject };
package/dist/index.js DELETED
@@ -1,96 +0,0 @@
1
- "use strict";
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
-
20
- // src/index.ts
21
- var index_exports = {};
22
- __export(index_exports, {
23
- getSignedDownloadUrl: () => getSignedDownloadUrl,
24
- getSignedUploadUrl: () => getSignedUploadUrl,
25
- initStorage: () => initStorage,
26
- putObject: () => putObject,
27
- validateUpload: () => validateUpload
28
- });
29
- module.exports = __toCommonJS(index_exports);
30
- var import_client_s3 = require("@aws-sdk/client-s3");
31
- var import_s3_request_presigner = require("@aws-sdk/s3-request-presigner");
32
-
33
- // src/shared.ts
34
- var import_errors = require("@wrelik/errors");
35
- function validateUpload(file, policy) {
36
- if (file.sizeBytes > policy.maxSizeBytes) {
37
- throw new import_errors.ValidationError(`File too large. Max ${policy.maxSizeBytes} bytes.`);
38
- }
39
- if (!policy.allowedTypes.includes(file.contentType)) {
40
- throw new import_errors.ValidationError(`Invalid file type ${file.contentType}`);
41
- }
42
- return true;
43
- }
44
-
45
- // src/index.ts
46
- var client;
47
- var bucket;
48
- function initStorage(config) {
49
- const endpoint = `https://${config.accountId}.r2.cloudflarestorage.com`;
50
- client = new import_client_s3.S3Client({
51
- region: config.region || "auto",
52
- endpoint,
53
- credentials: {
54
- accessKeyId: config.accessKeyId,
55
- secretAccessKey: config.secretAccessKey
56
- }
57
- });
58
- bucket = config.bucketName;
59
- }
60
- function getClient() {
61
- if (!client) throw new Error("Storage not initialized. Call initStorage first.");
62
- return client;
63
- }
64
- async function putObject(body, key, contentType, metadata) {
65
- const command = new import_client_s3.PutObjectCommand({
66
- Bucket: bucket,
67
- Key: key,
68
- Body: body,
69
- ContentType: contentType,
70
- Metadata: metadata
71
- });
72
- return getClient().send(command);
73
- }
74
- async function getSignedUploadUrl(key, contentType, expiresIn = 3600) {
75
- const command = new import_client_s3.PutObjectCommand({
76
- Bucket: bucket,
77
- Key: key,
78
- ContentType: contentType
79
- });
80
- return (0, import_s3_request_presigner.getSignedUrl)(getClient(), command, { expiresIn });
81
- }
82
- async function getSignedDownloadUrl(key, expiresIn = 3600) {
83
- const command = new import_client_s3.GetObjectCommand({
84
- Bucket: bucket,
85
- Key: key
86
- });
87
- return (0, import_s3_request_presigner.getSignedUrl)(getClient(), command, { expiresIn });
88
- }
89
- // Annotate the CommonJS export names for ESM import in node:
90
- 0 && (module.exports = {
91
- getSignedDownloadUrl,
92
- getSignedUploadUrl,
93
- initStorage,
94
- putObject,
95
- validateUpload
96
- });
package/dist/index.mjs DELETED
@@ -1,57 +0,0 @@
1
- import {
2
- validateUpload
3
- } from "./chunk-6VKCOC6I.mjs";
4
-
5
- // src/index.ts
6
- import { S3Client, PutObjectCommand, GetObjectCommand } from "@aws-sdk/client-s3";
7
- import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
8
- var client;
9
- var bucket;
10
- function initStorage(config) {
11
- const endpoint = `https://${config.accountId}.r2.cloudflarestorage.com`;
12
- client = new S3Client({
13
- region: config.region || "auto",
14
- endpoint,
15
- credentials: {
16
- accessKeyId: config.accessKeyId,
17
- secretAccessKey: config.secretAccessKey
18
- }
19
- });
20
- bucket = config.bucketName;
21
- }
22
- function getClient() {
23
- if (!client) throw new Error("Storage not initialized. Call initStorage first.");
24
- return client;
25
- }
26
- async function putObject(body, key, contentType, metadata) {
27
- const command = new PutObjectCommand({
28
- Bucket: bucket,
29
- Key: key,
30
- Body: body,
31
- ContentType: contentType,
32
- Metadata: metadata
33
- });
34
- return getClient().send(command);
35
- }
36
- async function getSignedUploadUrl(key, contentType, expiresIn = 3600) {
37
- const command = new PutObjectCommand({
38
- Bucket: bucket,
39
- Key: key,
40
- ContentType: contentType
41
- });
42
- return getSignedUrl(getClient(), command, { expiresIn });
43
- }
44
- async function getSignedDownloadUrl(key, expiresIn = 3600) {
45
- const command = new GetObjectCommand({
46
- Bucket: bucket,
47
- Key: key
48
- });
49
- return getSignedUrl(getClient(), command, { expiresIn });
50
- }
51
- export {
52
- getSignedDownloadUrl,
53
- getSignedUploadUrl,
54
- initStorage,
55
- putObject,
56
- validateUpload
57
- };
package/src/index.ts DELETED
@@ -1,73 +0,0 @@
1
- /* eslint-disable no-restricted-imports */
2
- import { S3Client, PutObjectCommand, GetObjectCommand } from '@aws-sdk/client-s3';
3
- import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
4
-
5
- export interface StorageConfig {
6
- accountId: string;
7
- accessKeyId: string;
8
- secretAccessKey: string;
9
- bucketName: string;
10
- region?: string;
11
- }
12
-
13
- let client: S3Client;
14
- let bucket: string;
15
-
16
- export function initStorage(config: StorageConfig) {
17
- // For R2, endpoint: https://<accountid>.r2.cloudflarestorage.com
18
- const endpoint = `https://${config.accountId}.r2.cloudflarestorage.com`;
19
-
20
- client = new S3Client({
21
- region: config.region || 'auto',
22
- endpoint,
23
- credentials: {
24
- accessKeyId: config.accessKeyId,
25
- secretAccessKey: config.secretAccessKey,
26
- },
27
- });
28
-
29
- bucket = config.bucketName;
30
- }
31
-
32
- function getClient() {
33
- if (!client) throw new Error('Storage not initialized. Call initStorage first.');
34
- return client;
35
- }
36
-
37
- export async function putObject(
38
- body: Buffer | Uint8Array | Blob | string,
39
- key: string,
40
- contentType: string,
41
- metadata?: Record<string, string>,
42
- ) {
43
- const command = new PutObjectCommand({
44
- Bucket: bucket,
45
- Key: key,
46
- Body: body,
47
- ContentType: contentType,
48
- Metadata: metadata,
49
- });
50
-
51
- return getClient().send(command);
52
- }
53
-
54
- export async function getSignedUploadUrl(key: string, contentType: string, expiresIn = 3600) {
55
- const command = new PutObjectCommand({
56
- Bucket: bucket,
57
- Key: key,
58
- ContentType: contentType,
59
- });
60
-
61
- return getSignedUrl(getClient(), command, { expiresIn });
62
- }
63
-
64
- export async function getSignedDownloadUrl(key: string, expiresIn = 3600) {
65
- const command = new GetObjectCommand({
66
- Bucket: bucket,
67
- Key: key,
68
- });
69
-
70
- return getSignedUrl(getClient(), command, { expiresIn });
71
- }
72
-
73
- export * from './shared';