@voyant-travel/storage 0.104.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +50 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/lib/sigv4.d.ts +50 -0
- package/dist/lib/sigv4.d.ts.map +1 -0
- package/dist/lib/sigv4.js +165 -0
- package/dist/providers/local.d.ts +26 -0
- package/dist/providers/local.d.ts.map +1 -0
- package/dist/providers/local.js +54 -0
- package/dist/providers/r2.d.ts +71 -0
- package/dist/providers/r2.d.ts.map +1 -0
- package/dist/providers/r2.js +62 -0
- package/dist/providers/s3.d.ts +57 -0
- package/dist/providers/s3.d.ts.map +1 -0
- package/dist/providers/s3.js +141 -0
- package/dist/service.d.ts +21 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +22 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +69 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity. For the purposes of this definition,
|
|
18
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
19
|
+
direction or management of such entity, whether by contract or
|
|
20
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
21
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
22
|
+
|
|
23
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
24
|
+
exercising permissions granted by this License.
|
|
25
|
+
|
|
26
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
27
|
+
including but not limited to software source code, documentation
|
|
28
|
+
source, and configuration files.
|
|
29
|
+
|
|
30
|
+
"Object" form shall mean any form resulting from mechanical
|
|
31
|
+
transformation or translation of a Source form, including but
|
|
32
|
+
not limited to compiled object code, generated documentation,
|
|
33
|
+
and conversions to other media types.
|
|
34
|
+
|
|
35
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
36
|
+
Object form, made available under the License, as indicated by a
|
|
37
|
+
copyright notice that is included in or attached to the work
|
|
38
|
+
(an example is provided in the Appendix below).
|
|
39
|
+
|
|
40
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
41
|
+
form, that is based on (or derived from) the Work and for which the
|
|
42
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
43
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
44
|
+
of this License, Derivative Works shall not include works that remain
|
|
45
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
46
|
+
the Work and Derivative Works thereof.
|
|
47
|
+
|
|
48
|
+
"Contribution" shall mean any work of authorship, including
|
|
49
|
+
the original version of the Work and any modifications or additions
|
|
50
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
51
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
52
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
53
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
54
|
+
means any form of electronic, verbal, or written communication sent
|
|
55
|
+
to the Licensor or its representatives, including but not limited to
|
|
56
|
+
communication on electronic mailing lists, source code control systems,
|
|
57
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
58
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
59
|
+
excluding communication that is conspicuously marked or otherwise
|
|
60
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
61
|
+
|
|
62
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
63
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
64
|
+
subsequently incorporated within the Work.
|
|
65
|
+
|
|
66
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
67
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
68
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
69
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
70
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
71
|
+
Work and such Derivative Works in Source or Object form.
|
|
72
|
+
|
|
73
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
74
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
75
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
76
|
+
(except as stated in this section) patent license to make, have made,
|
|
77
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
78
|
+
where such license applies only to those patent claims licensable
|
|
79
|
+
by such Contributor that are necessarily infringed by their
|
|
80
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
81
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
82
|
+
institute patent litigation against any entity (including a
|
|
83
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
84
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
85
|
+
or contributory patent infringement, then any patent licenses
|
|
86
|
+
granted to You under this License for that Work shall terminate
|
|
87
|
+
as of the date such litigation is filed.
|
|
88
|
+
|
|
89
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
90
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
91
|
+
modifications, and in Source or Object form, provided that You
|
|
92
|
+
meet the following conditions:
|
|
93
|
+
|
|
94
|
+
(a) You must give any other recipients of the Work or
|
|
95
|
+
Derivative Works a copy of this License; and
|
|
96
|
+
|
|
97
|
+
(b) You must cause any modified files to carry prominent notices
|
|
98
|
+
stating that You changed the files; and
|
|
99
|
+
|
|
100
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
101
|
+
that You distribute, all copyright, patent, trademark, and
|
|
102
|
+
attribution notices from the Source form of the Work,
|
|
103
|
+
excluding those notices that do not pertain to any part of
|
|
104
|
+
the Derivative Works; and
|
|
105
|
+
|
|
106
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
107
|
+
distribution, then any Derivative Works that You distribute must
|
|
108
|
+
include a readable copy of the attribution notices contained
|
|
109
|
+
within such NOTICE file, excluding those notices that do not
|
|
110
|
+
pertain to any part of the Derivative Works, in at least one
|
|
111
|
+
of the following places: within a NOTICE text file distributed
|
|
112
|
+
as part of the Derivative Works; within the Source form or
|
|
113
|
+
documentation, if provided along with the Derivative Works; or,
|
|
114
|
+
within a display generated by the Derivative Works, if and
|
|
115
|
+
wherever such third-party notices normally appear. The contents
|
|
116
|
+
of the NOTICE file are for informational purposes only and
|
|
117
|
+
do not modify the License. You may add Your own attribution
|
|
118
|
+
notices within Derivative Works that You distribute, alongside
|
|
119
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
120
|
+
that such additional attribution notices cannot be construed
|
|
121
|
+
as modifying the License.
|
|
122
|
+
|
|
123
|
+
You may add Your own copyright statement to Your modifications and
|
|
124
|
+
may provide additional or different license terms and conditions
|
|
125
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
126
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
127
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
128
|
+
the conditions stated in this License.
|
|
129
|
+
|
|
130
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
131
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
132
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
133
|
+
this License, without any additional terms or conditions.
|
|
134
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
135
|
+
the terms of any separate license agreement you may have executed
|
|
136
|
+
with Licensor regarding such Contributions.
|
|
137
|
+
|
|
138
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
139
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
140
|
+
except as required for describing the origin of the Work and
|
|
141
|
+
reproducing the content of the NOTICE file.
|
|
142
|
+
|
|
143
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
144
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
145
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
146
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
147
|
+
implied, including, without limitation, any warranties or conditions
|
|
148
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
149
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
150
|
+
appropriateness of using or redistributing the Work and assume any
|
|
151
|
+
risks associated with Your exercise of permissions under this License.
|
|
152
|
+
|
|
153
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
154
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
155
|
+
unless required by applicable law (such as deliberate and grossly
|
|
156
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
157
|
+
liable to You for damages, including any direct, indirect, special,
|
|
158
|
+
incidental, or consequential damages of any character arising as a
|
|
159
|
+
result of this License or out of the use or inability to use the
|
|
160
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
161
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
162
|
+
other commercial damages or losses), even if such Contributor
|
|
163
|
+
has been advised of the possibility of such damages.
|
|
164
|
+
|
|
165
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
166
|
+
the Work or Derivative Works thereof, You may accept and charge a
|
|
167
|
+
fee for, the acceptance of support, warranty, indemnity, or
|
|
168
|
+
other liability obligations and/or rights consistent with this
|
|
169
|
+
License. However, in accepting such obligations, You may act only
|
|
170
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
171
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
172
|
+
defend, and hold each Contributor harmless for any liability
|
|
173
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
174
|
+
of your accepting any such warranty or additional liability.
|
|
175
|
+
|
|
176
|
+
END OF TERMS AND CONDITIONS
|
|
177
|
+
|
|
178
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
179
|
+
|
|
180
|
+
To apply the Apache License to your work, attach the following
|
|
181
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
182
|
+
replaced with your own identifying information. (Don't include
|
|
183
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
184
|
+
comment syntax for the file format. We also recommend that a
|
|
185
|
+
file or class name and description of purpose be included on the
|
|
186
|
+
same "printed page" as the copyright notice for easier
|
|
187
|
+
identification within third-party archives.
|
|
188
|
+
|
|
189
|
+
Copyright 2026 PixelMakers Studio SRL
|
|
190
|
+
|
|
191
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
192
|
+
you may not use this file except in compliance with the License.
|
|
193
|
+
You may obtain a copy of the License at
|
|
194
|
+
|
|
195
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
196
|
+
|
|
197
|
+
Unless required by applicable law or agreed to in writing, software
|
|
198
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
199
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
200
|
+
See the License for the specific language governing permissions and
|
|
201
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# @voyant-travel/storage
|
|
2
|
+
|
|
3
|
+
Storage provider abstraction for Voyant. `StorageProvider` interface plus providers for local (in-memory), Cloudflare R2, and S3-compatible (AWS SigV4 via Web Crypto — works in Cloudflare Workers).
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add @voyant-travel/storage
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createStorageService } from "@voyant-travel/storage"
|
|
15
|
+
import { s3Provider } from "@voyant-travel/storage/providers/s3"
|
|
16
|
+
|
|
17
|
+
const storage = createStorageService(
|
|
18
|
+
s3Provider({
|
|
19
|
+
region: "us-east-1",
|
|
20
|
+
bucket: "my-bucket",
|
|
21
|
+
accessKeyId: env.AWS_ACCESS_KEY_ID,
|
|
22
|
+
secretAccessKey: env.AWS_SECRET_ACCESS_KEY,
|
|
23
|
+
}),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
await storage.upload({ key: "files/x.pdf", body: buffer })
|
|
27
|
+
const url = await storage.signedUrl({ key: "files/x.pdf", expiresIn: 300 })
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The S3 provider supports `forcePathStyle` and a custom `endpoint` for S3-compatible services (Wasabi, MinIO, etc.). SigV4 signing is verified against AWS canonical test vectors.
|
|
31
|
+
|
|
32
|
+
The R2 binding provider cannot mint signed URLs by itself. Configure either
|
|
33
|
+
`publicBaseUrl` or a custom `signer` before calling `signedUrl`; otherwise the
|
|
34
|
+
provider throws instead of returning a raw storage key.
|
|
35
|
+
|
|
36
|
+
## Exports
|
|
37
|
+
|
|
38
|
+
| Entry | Description |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `.` | Barrel re-exports |
|
|
41
|
+
| `./types` | `StorageProvider` interface |
|
|
42
|
+
| `./service` | `createStorageService` |
|
|
43
|
+
| `./providers/local` | In-memory provider |
|
|
44
|
+
| `./providers/r2` | Cloudflare R2 binding provider |
|
|
45
|
+
| `./providers/s3` | S3 provider with SigV4 |
|
|
46
|
+
| `./lib/sigv4` | `signRequest`, `presignUrl` primitives |
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
Apache-2.0
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type { PresignUrlInput, SignedRequestHeaders, SignRequestInput, SigV4Context, SigV4Credentials, } from "./lib/sigv4.js";
|
|
2
|
+
export { presignUrl, signRequest } from "./lib/sigv4.js";
|
|
3
|
+
export type { LocalStorageOptions } from "./providers/local.js";
|
|
4
|
+
export { createLocalStorageProvider } from "./providers/local.js";
|
|
5
|
+
export type { R2BucketLike, R2ObjectLike, R2ProviderOptions, R2PutOptionsLike, } from "./providers/r2.js";
|
|
6
|
+
export { createR2Provider } from "./providers/r2.js";
|
|
7
|
+
export type { S3Fetch, S3ProviderOptions } from "./providers/s3.js";
|
|
8
|
+
export { createS3Provider } from "./providers/s3.js";
|
|
9
|
+
export type { StorageService } from "./service.js";
|
|
10
|
+
export { createStorageService, StorageError } from "./service.js";
|
|
11
|
+
export type { StorageObject, StorageProvider, StorageUploadBody, UploadOptions, } from "./types.js";
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,eAAe,EACf,oBAAoB,EACpB,gBAAgB,EAChB,YAAY,EACZ,gBAAgB,GACjB,MAAM,gBAAgB,CAAA;AACvB,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACxD,YAAY,EAAE,mBAAmB,EAAE,MAAM,sBAAsB,CAAA;AAC/D,OAAO,EAAE,0BAA0B,EAAE,MAAM,sBAAsB,CAAA;AACjE,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,GACjB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,YAAY,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACnE,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AACjE,YAAY,EACV,aAAa,EACb,eAAe,EACf,iBAAiB,EACjB,aAAa,GACd,MAAM,YAAY,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { presignUrl, signRequest } from "./lib/sigv4.js";
|
|
2
|
+
export { createLocalStorageProvider } from "./providers/local.js";
|
|
3
|
+
export { createR2Provider } from "./providers/r2.js";
|
|
4
|
+
export { createS3Provider } from "./providers/s3.js";
|
|
5
|
+
export { createStorageService, StorageError } from "./service.js";
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal AWS SigV4 signing implementation using Web Crypto. Works in
|
|
3
|
+
* Cloudflare Workers, modern Node, Deno, and browsers.
|
|
4
|
+
*
|
|
5
|
+
* Supports two use cases needed by the S3 storage provider:
|
|
6
|
+
* - `signRequest`: attach an `Authorization` header for a direct request
|
|
7
|
+
* - `presignUrl`: produce a time-limited URL via query-string signing
|
|
8
|
+
*
|
|
9
|
+
* Reference:
|
|
10
|
+
* https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv_create-signed-request.html
|
|
11
|
+
*/
|
|
12
|
+
export interface SigV4Credentials {
|
|
13
|
+
accessKeyId: string;
|
|
14
|
+
secretAccessKey: string;
|
|
15
|
+
sessionToken?: string;
|
|
16
|
+
}
|
|
17
|
+
export interface SigV4Context {
|
|
18
|
+
credentials: SigV4Credentials;
|
|
19
|
+
region: string;
|
|
20
|
+
service: string;
|
|
21
|
+
}
|
|
22
|
+
export interface SignRequestInput extends SigV4Context {
|
|
23
|
+
method: string;
|
|
24
|
+
url: string;
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
body?: Uint8Array;
|
|
27
|
+
/** Override "now" (milliseconds since epoch). Useful for tests. */
|
|
28
|
+
now?: number;
|
|
29
|
+
}
|
|
30
|
+
export interface SignedRequestHeaders {
|
|
31
|
+
headers: Record<string, string>;
|
|
32
|
+
}
|
|
33
|
+
export interface PresignUrlInput extends SigV4Context {
|
|
34
|
+
method: string;
|
|
35
|
+
url: string;
|
|
36
|
+
expiresIn: number;
|
|
37
|
+
/** Extra signed headers beyond the default `host`. */
|
|
38
|
+
headers?: Record<string, string>;
|
|
39
|
+
/** Override "now" (milliseconds since epoch). Useful for tests. */
|
|
40
|
+
now?: number;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Sign a request and return the `Authorization` (and related) headers.
|
|
44
|
+
*/
|
|
45
|
+
export declare function signRequest(input: SignRequestInput): Promise<SignedRequestHeaders>;
|
|
46
|
+
/**
|
|
47
|
+
* Produce a presigned URL (query-string signing) for the given method.
|
|
48
|
+
*/
|
|
49
|
+
export declare function presignUrl(input: PresignUrlInput): Promise<string>;
|
|
50
|
+
//# sourceMappingURL=sigv4.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sigv4.d.ts","sourceRoot":"","sources":["../../src/lib/sigv4.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,gBAAgB,CAAA;IAC7B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,gBAAiB,SAAQ,YAAY;IACpD,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAChC;AAED,MAAM,WAAW,eAAgB,SAAQ,YAAY;IACnD,MAAM,EAAE,MAAM,CAAA;IACd,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAChC,mEAAmE;IACnE,GAAG,CAAC,EAAE,MAAM,CAAA;CACb;AAED;;GAEG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAqDxF;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,CAgDxE"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal AWS SigV4 signing implementation using Web Crypto. Works in
|
|
3
|
+
* Cloudflare Workers, modern Node, Deno, and browsers.
|
|
4
|
+
*
|
|
5
|
+
* Supports two use cases needed by the S3 storage provider:
|
|
6
|
+
* - `signRequest`: attach an `Authorization` header for a direct request
|
|
7
|
+
* - `presignUrl`: produce a time-limited URL via query-string signing
|
|
8
|
+
*
|
|
9
|
+
* Reference:
|
|
10
|
+
* https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv_create-signed-request.html
|
|
11
|
+
*/
|
|
12
|
+
const encoder = new TextEncoder();
|
|
13
|
+
/**
|
|
14
|
+
* Sign a request and return the `Authorization` (and related) headers.
|
|
15
|
+
*/
|
|
16
|
+
export async function signRequest(input) {
|
|
17
|
+
const { amzDate, dateStamp } = datesFromNow(input.now);
|
|
18
|
+
const url = new URL(input.url);
|
|
19
|
+
const bodyBytes = input.body ?? new Uint8Array();
|
|
20
|
+
const payloadHash = await hexHash(bodyBytes);
|
|
21
|
+
const baseHeaders = {
|
|
22
|
+
...(input.headers ?? {}),
|
|
23
|
+
host: url.host,
|
|
24
|
+
"x-amz-date": amzDate,
|
|
25
|
+
"x-amz-content-sha256": payloadHash,
|
|
26
|
+
};
|
|
27
|
+
if (input.credentials.sessionToken) {
|
|
28
|
+
baseHeaders["x-amz-security-token"] = input.credentials.sessionToken;
|
|
29
|
+
}
|
|
30
|
+
const canonicalQuery = canonicalQueryString(url);
|
|
31
|
+
const { canonicalHeaders, signedHeaders } = canonicalizeHeaders(baseHeaders);
|
|
32
|
+
const canonicalRequest = [
|
|
33
|
+
input.method.toUpperCase(),
|
|
34
|
+
canonicalUri(url.pathname),
|
|
35
|
+
canonicalQuery,
|
|
36
|
+
canonicalHeaders,
|
|
37
|
+
signedHeaders,
|
|
38
|
+
payloadHash,
|
|
39
|
+
].join("\n");
|
|
40
|
+
const scope = `${dateStamp}/${input.region}/${input.service}/aws4_request`;
|
|
41
|
+
const stringToSign = [
|
|
42
|
+
"AWS4-HMAC-SHA256",
|
|
43
|
+
amzDate,
|
|
44
|
+
scope,
|
|
45
|
+
await hexHash(encoder.encode(canonicalRequest)),
|
|
46
|
+
].join("\n");
|
|
47
|
+
const signingKey = await deriveSigningKey(input.credentials.secretAccessKey, dateStamp, input.region, input.service);
|
|
48
|
+
const signature = hex(await hmac(signingKey, stringToSign));
|
|
49
|
+
const authHeader = `AWS4-HMAC-SHA256 Credential=${input.credentials.accessKeyId}/${scope}` +
|
|
50
|
+
`, SignedHeaders=${signedHeaders}, Signature=${signature}`;
|
|
51
|
+
return {
|
|
52
|
+
headers: {
|
|
53
|
+
...baseHeaders,
|
|
54
|
+
Authorization: authHeader,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Produce a presigned URL (query-string signing) for the given method.
|
|
60
|
+
*/
|
|
61
|
+
export async function presignUrl(input) {
|
|
62
|
+
const { amzDate, dateStamp } = datesFromNow(input.now);
|
|
63
|
+
const url = new URL(input.url);
|
|
64
|
+
const scope = `${dateStamp}/${input.region}/${input.service}/aws4_request`;
|
|
65
|
+
const headers = {
|
|
66
|
+
...(input.headers ?? {}),
|
|
67
|
+
host: url.host,
|
|
68
|
+
};
|
|
69
|
+
const { canonicalHeaders, signedHeaders } = canonicalizeHeaders(headers);
|
|
70
|
+
const params = new URLSearchParams(url.searchParams);
|
|
71
|
+
params.set("X-Amz-Algorithm", "AWS4-HMAC-SHA256");
|
|
72
|
+
params.set("X-Amz-Credential", `${input.credentials.accessKeyId}/${scope}`);
|
|
73
|
+
params.set("X-Amz-Date", amzDate);
|
|
74
|
+
params.set("X-Amz-Expires", String(input.expiresIn));
|
|
75
|
+
params.set("X-Amz-SignedHeaders", signedHeaders);
|
|
76
|
+
if (input.credentials.sessionToken) {
|
|
77
|
+
params.set("X-Amz-Security-Token", input.credentials.sessionToken);
|
|
78
|
+
}
|
|
79
|
+
url.search = params.toString();
|
|
80
|
+
const canonicalRequest = [
|
|
81
|
+
input.method.toUpperCase(),
|
|
82
|
+
canonicalUri(url.pathname),
|
|
83
|
+
canonicalQueryString(url),
|
|
84
|
+
canonicalHeaders,
|
|
85
|
+
signedHeaders,
|
|
86
|
+
"UNSIGNED-PAYLOAD",
|
|
87
|
+
].join("\n");
|
|
88
|
+
const stringToSign = [
|
|
89
|
+
"AWS4-HMAC-SHA256",
|
|
90
|
+
amzDate,
|
|
91
|
+
scope,
|
|
92
|
+
await hexHash(encoder.encode(canonicalRequest)),
|
|
93
|
+
].join("\n");
|
|
94
|
+
const signingKey = await deriveSigningKey(input.credentials.secretAccessKey, dateStamp, input.region, input.service);
|
|
95
|
+
const signature = hex(await hmac(signingKey, stringToSign));
|
|
96
|
+
params.set("X-Amz-Signature", signature);
|
|
97
|
+
url.search = params.toString();
|
|
98
|
+
return url.toString();
|
|
99
|
+
}
|
|
100
|
+
// --- helpers --- //
|
|
101
|
+
function datesFromNow(nowMs) {
|
|
102
|
+
const d = new Date(nowMs ?? Date.now());
|
|
103
|
+
const iso = d.toISOString().replace(/[:-]|\.\d{3}/g, "");
|
|
104
|
+
// iso is like "20251005T223045Z"
|
|
105
|
+
return { amzDate: iso, dateStamp: iso.slice(0, 8) };
|
|
106
|
+
}
|
|
107
|
+
function canonicalUri(path) {
|
|
108
|
+
// S3 keys should be path-encoded but preserve "/"
|
|
109
|
+
if (!path)
|
|
110
|
+
return "/";
|
|
111
|
+
return path
|
|
112
|
+
.split("/")
|
|
113
|
+
.map((segment) => encodeRfc3986(segment))
|
|
114
|
+
.join("/");
|
|
115
|
+
}
|
|
116
|
+
function canonicalQueryString(url) {
|
|
117
|
+
const pairs = [];
|
|
118
|
+
for (const [key, value] of url.searchParams.entries()) {
|
|
119
|
+
pairs.push([encodeRfc3986(key), encodeRfc3986(value)]);
|
|
120
|
+
}
|
|
121
|
+
pairs.sort((a, b) => (a[0] === b[0] ? (a[1] < b[1] ? -1 : 1) : a[0] < b[0] ? -1 : 1));
|
|
122
|
+
return pairs.map(([k, v]) => `${k}=${v}`).join("&");
|
|
123
|
+
}
|
|
124
|
+
function canonicalizeHeaders(headers) {
|
|
125
|
+
const entries = Object.entries(headers)
|
|
126
|
+
.map(([k, v]) => [k.toLowerCase(), v.trim().replace(/\s+/g, " ")])
|
|
127
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
128
|
+
const canonicalHeaders = `${entries.map(([k, v]) => `${k}:${v}`).join("\n")}\n`;
|
|
129
|
+
const signedHeaders = entries.map(([k]) => k).join(";");
|
|
130
|
+
return { canonicalHeaders, signedHeaders };
|
|
131
|
+
}
|
|
132
|
+
function encodeRfc3986(value) {
|
|
133
|
+
return encodeURIComponent(value).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
134
|
+
}
|
|
135
|
+
async function hexHash(data) {
|
|
136
|
+
const copy = new Uint8Array(data.byteLength);
|
|
137
|
+
copy.set(data);
|
|
138
|
+
const digest = await crypto.subtle.digest("SHA-256", copy);
|
|
139
|
+
return hex(digest);
|
|
140
|
+
}
|
|
141
|
+
async function hmac(key, data) {
|
|
142
|
+
const source = key instanceof Uint8Array ? key : new Uint8Array(key);
|
|
143
|
+
const keyBytes = new Uint8Array(source.byteLength);
|
|
144
|
+
keyBytes.set(source);
|
|
145
|
+
const cryptoKey = await crypto.subtle.importKey("raw", keyBytes, { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
|
|
146
|
+
const payloadSource = encoder.encode(data);
|
|
147
|
+
const payload = new Uint8Array(payloadSource.byteLength);
|
|
148
|
+
payload.set(payloadSource);
|
|
149
|
+
return crypto.subtle.sign("HMAC", cryptoKey, payload);
|
|
150
|
+
}
|
|
151
|
+
async function deriveSigningKey(secret, dateStamp, region, service) {
|
|
152
|
+
const kDate = await hmac(encoder.encode(`AWS4${secret}`), dateStamp);
|
|
153
|
+
const kRegion = await hmac(kDate, region);
|
|
154
|
+
const kService = await hmac(kRegion, service);
|
|
155
|
+
const kSigning = await hmac(kService, "aws4_request");
|
|
156
|
+
return kSigning;
|
|
157
|
+
}
|
|
158
|
+
function hex(buffer) {
|
|
159
|
+
const bytes = new Uint8Array(buffer);
|
|
160
|
+
let out = "";
|
|
161
|
+
for (const b of bytes) {
|
|
162
|
+
out += b.toString(16).padStart(2, "0");
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { StorageProvider } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Options for {@link createLocalStorageProvider}.
|
|
4
|
+
*/
|
|
5
|
+
export interface LocalStorageOptions {
|
|
6
|
+
/** Provider name (defaults to `"local"`). */
|
|
7
|
+
name?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Base URL used to construct the string returned from `signedUrl` and
|
|
10
|
+
* `upload`. Defaults to `"local://"`. The final URL is `${baseUrl}${key}`.
|
|
11
|
+
*/
|
|
12
|
+
baseUrl?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Function used to mint random keys when `UploadOptions.key` is not
|
|
15
|
+
* provided. Defaults to `crypto.randomUUID()` via the global `crypto`.
|
|
16
|
+
*/
|
|
17
|
+
generateKey?: () => string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create an in-memory storage provider. Useful for unit tests and for
|
|
21
|
+
* locally running workflows without touching remote storage. Data is
|
|
22
|
+
* kept in a `Map` held inside the closure and is lost when the process
|
|
23
|
+
* exits.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createLocalStorageProvider(options?: LocalStorageOptions): StorageProvider;
|
|
26
|
+
//# sourceMappingURL=local.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local.d.ts","sourceRoot":"","sources":["../../src/providers/local.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,eAAe,EAAoC,MAAM,aAAa,CAAA;AAEnG;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,6CAA6C;IAC7C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,MAAM,CAAA;CAC3B;AAQD;;;;;GAKG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,GAAE,mBAAwB,GAAG,eAAe,CAwC7F"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Create an in-memory storage provider. Useful for unit tests and for
|
|
3
|
+
* locally running workflows without touching remote storage. Data is
|
|
4
|
+
* kept in a `Map` held inside the closure and is lost when the process
|
|
5
|
+
* exits.
|
|
6
|
+
*/
|
|
7
|
+
export function createLocalStorageProvider(options = {}) {
|
|
8
|
+
const name = options.name ?? "local";
|
|
9
|
+
const baseUrl = options.baseUrl ?? "local://";
|
|
10
|
+
const generateKey = options.generateKey ??
|
|
11
|
+
(() => {
|
|
12
|
+
const g = globalThis;
|
|
13
|
+
return g.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
14
|
+
});
|
|
15
|
+
const store = new Map();
|
|
16
|
+
async function upload(body, opts = {}) {
|
|
17
|
+
const key = opts.key ?? generateKey();
|
|
18
|
+
const bytes = await toBytes(body);
|
|
19
|
+
const record = { bytes };
|
|
20
|
+
if (opts.contentType !== undefined)
|
|
21
|
+
record.contentType = opts.contentType;
|
|
22
|
+
if (opts.metadata !== undefined)
|
|
23
|
+
record.metadata = opts.metadata;
|
|
24
|
+
store.set(key, record);
|
|
25
|
+
return { key, url: `${baseUrl}${key}` };
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
name,
|
|
29
|
+
upload,
|
|
30
|
+
async delete(key) {
|
|
31
|
+
store.delete(key);
|
|
32
|
+
},
|
|
33
|
+
async signedUrl(key) {
|
|
34
|
+
return `${baseUrl}${key}`;
|
|
35
|
+
},
|
|
36
|
+
async get(key) {
|
|
37
|
+
const record = store.get(key);
|
|
38
|
+
if (!record)
|
|
39
|
+
return null;
|
|
40
|
+
// Copy into a fresh ArrayBuffer so downstream mutation can't corrupt the store.
|
|
41
|
+
const copy = new Uint8Array(record.bytes.byteLength);
|
|
42
|
+
copy.set(record.bytes);
|
|
43
|
+
return copy.buffer;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
async function toBytes(body) {
|
|
48
|
+
if (body instanceof Uint8Array)
|
|
49
|
+
return body;
|
|
50
|
+
if (body instanceof ArrayBuffer)
|
|
51
|
+
return new Uint8Array(body);
|
|
52
|
+
const buffer = await body.arrayBuffer();
|
|
53
|
+
return new Uint8Array(buffer);
|
|
54
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { StorageProvider } from "../types.js";
|
|
2
|
+
export declare const R2_SIGNED_URL_CONFIGURATION_ERROR_MESSAGE = "R2 provider: signedUrl requires a `signer` to be configured \u2014 a public base URL cannot produce a time-limited URL. Use publicUrl(key) for permanent public URLs, or configure a signer (e.g. R2's S3-compatible API with SigV4 credentials).";
|
|
3
|
+
/**
|
|
4
|
+
* Subset of the Cloudflare Workers `R2Bucket` binding we depend on. Kept
|
|
5
|
+
* as a minimal structural type so this package does not need a runtime
|
|
6
|
+
* dependency on `@cloudflare/workers-types`.
|
|
7
|
+
*/
|
|
8
|
+
export interface R2BucketLike {
|
|
9
|
+
put(key: string, value: ArrayBuffer | ArrayBufferView | Blob | string | ReadableStream | null, options?: R2PutOptionsLike): Promise<unknown>;
|
|
10
|
+
delete(key: string | string[]): Promise<void>;
|
|
11
|
+
get(key: string): Promise<R2ObjectLike | null>;
|
|
12
|
+
}
|
|
13
|
+
export interface R2PutOptionsLike {
|
|
14
|
+
httpMetadata?: {
|
|
15
|
+
contentType?: string;
|
|
16
|
+
};
|
|
17
|
+
customMetadata?: Record<string, string>;
|
|
18
|
+
}
|
|
19
|
+
export interface R2ObjectLike {
|
|
20
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Options for {@link createR2Provider}.
|
|
24
|
+
*/
|
|
25
|
+
export interface R2ProviderOptions {
|
|
26
|
+
/** Cloudflare R2 bucket binding (from `env.BUCKET_NAME`). */
|
|
27
|
+
bucket: R2BucketLike;
|
|
28
|
+
/**
|
|
29
|
+
* Base URL used to construct public object URLs. Typical values:
|
|
30
|
+
* - a public R2 custom domain: `https://cdn.example.com/`
|
|
31
|
+
* - a Worker route that proxies to the binding: `https://api.example.com/assets/`
|
|
32
|
+
*/
|
|
33
|
+
publicBaseUrl?: string;
|
|
34
|
+
/**
|
|
35
|
+
* Signer invoked by `signedUrl`. Cloudflare R2 bindings do not produce
|
|
36
|
+
* signed URLs directly; templates pass a custom signer that either:
|
|
37
|
+
* - returns a short-lived Worker route URL, or
|
|
38
|
+
* - calls R2's S3-compatible API with SigV4 credentials.
|
|
39
|
+
* Calling `signedUrl` without a signer is a configuration error: a
|
|
40
|
+
* `publicBaseUrl` alone can only produce **permanent, unauthenticated**
|
|
41
|
+
* URLs, which would silently ignore `expiresIn` and defeat the
|
|
42
|
+
* time-limited-access contract. Use `publicUrl(key)` when a permanent
|
|
43
|
+
* public URL is actually what you want.
|
|
44
|
+
*/
|
|
45
|
+
signer?: (key: string, expiresIn: number) => Promise<string> | string;
|
|
46
|
+
/** Provider name (defaults to `"r2"`). */
|
|
47
|
+
name?: string;
|
|
48
|
+
/** Custom key generator; defaults to `crypto.randomUUID()`. */
|
|
49
|
+
generateKey?: () => string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* R2 storage provider. Extends the base {@link StorageProvider} contract
|
|
53
|
+
* with {@link R2StorageProvider.publicUrl} for the (R2-common) case where
|
|
54
|
+
* objects are served through a public custom domain or Worker route.
|
|
55
|
+
*/
|
|
56
|
+
export interface R2StorageProvider extends StorageProvider {
|
|
57
|
+
/**
|
|
58
|
+
* Permanent, unauthenticated URL for an object: `${publicBaseUrl}${key}`.
|
|
59
|
+
* Only valid for objects that are meant to be public. Throws when
|
|
60
|
+
* `publicBaseUrl` is not configured. For time-limited access to private
|
|
61
|
+
* objects use `signedUrl` (requires a `signer`).
|
|
62
|
+
*/
|
|
63
|
+
publicUrl(key: string): string;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Create a Cloudflare R2 storage provider bound to an R2 bucket binding.
|
|
67
|
+
* The R2 binding handles authentication transparently at the Worker
|
|
68
|
+
* runtime boundary, so no credentials are required at this layer.
|
|
69
|
+
*/
|
|
70
|
+
export declare function createR2Provider(options: R2ProviderOptions): R2StorageProvider;
|
|
71
|
+
//# sourceMappingURL=r2.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"r2.d.ts","sourceRoot":"","sources":["../../src/providers/r2.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,eAAe,EAAoC,MAAM,aAAa,CAAA;AAEnG,eAAO,MAAM,yCAAyC,sPAC0L,CAAA;AAEhP;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,GAAG,CACD,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,WAAW,GAAG,eAAe,GAAG,IAAI,GAAG,MAAM,GAAG,cAAc,GAAG,IAAI,EAC5E,OAAO,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,OAAO,CAAC,CAAA;IACnB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAA;CAC/C;AAED,MAAM,WAAW,gBAAgB;IAC/B,YAAY,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACxC;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,IAAI,OAAO,CAAC,WAAW,CAAC,CAAA;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,6DAA6D;IAC7D,MAAM,EAAE,YAAY,CAAA;IACpB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAA;IACrE,0CAA0C;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,MAAM,CAAA;CAC3B;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAkB,SAAQ,eAAe;IACxD;;;;;OAKG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAA;CAC/B;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,iBAAiB,CAoD9E"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export const R2_SIGNED_URL_CONFIGURATION_ERROR_MESSAGE = "R2 provider: signedUrl requires a `signer` to be configured — a public base URL cannot produce a time-limited URL. Use publicUrl(key) for permanent public URLs, or configure a signer (e.g. R2's S3-compatible API with SigV4 credentials).";
|
|
2
|
+
/**
|
|
3
|
+
* Create a Cloudflare R2 storage provider bound to an R2 bucket binding.
|
|
4
|
+
* The R2 binding handles authentication transparently at the Worker
|
|
5
|
+
* runtime boundary, so no credentials are required at this layer.
|
|
6
|
+
*/
|
|
7
|
+
export function createR2Provider(options) {
|
|
8
|
+
const name = options.name ?? "r2";
|
|
9
|
+
const publicBaseUrl = options.publicBaseUrl ?? "";
|
|
10
|
+
const generateKey = options.generateKey ??
|
|
11
|
+
(() => {
|
|
12
|
+
const g = globalThis;
|
|
13
|
+
return g.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
14
|
+
});
|
|
15
|
+
async function upload(body, opts = {}) {
|
|
16
|
+
const key = opts.key ?? generateKey();
|
|
17
|
+
const putOptions = {};
|
|
18
|
+
if (opts.contentType !== undefined) {
|
|
19
|
+
putOptions.httpMetadata = { contentType: opts.contentType };
|
|
20
|
+
}
|
|
21
|
+
if (opts.metadata !== undefined) {
|
|
22
|
+
putOptions.customMetadata = opts.metadata;
|
|
23
|
+
}
|
|
24
|
+
await options.bucket.put(key, await toPutBody(body), putOptions);
|
|
25
|
+
return { key, url: publicBaseUrl ? `${publicBaseUrl}${key}` : "" };
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
name,
|
|
29
|
+
upload,
|
|
30
|
+
async delete(key) {
|
|
31
|
+
await options.bucket.delete(key);
|
|
32
|
+
},
|
|
33
|
+
async signedUrl(key, expiresIn) {
|
|
34
|
+
// Security (assessment L7): never fall back to `${publicBaseUrl}${key}`
|
|
35
|
+
// here — that would silently return a PERMANENT public URL while the
|
|
36
|
+
// caller believes it expires after `expiresIn` seconds.
|
|
37
|
+
if (!options.signer) {
|
|
38
|
+
throw new Error(R2_SIGNED_URL_CONFIGURATION_ERROR_MESSAGE);
|
|
39
|
+
}
|
|
40
|
+
return options.signer(key, expiresIn);
|
|
41
|
+
},
|
|
42
|
+
publicUrl(key) {
|
|
43
|
+
if (!publicBaseUrl) {
|
|
44
|
+
throw new Error("R2 provider: publicUrl requires `publicBaseUrl` to be configured (a public R2 custom domain or a Worker route that proxies the bucket)");
|
|
45
|
+
}
|
|
46
|
+
return `${publicBaseUrl}${key}`;
|
|
47
|
+
},
|
|
48
|
+
async get(key) {
|
|
49
|
+
const obj = await options.bucket.get(key);
|
|
50
|
+
if (!obj)
|
|
51
|
+
return null;
|
|
52
|
+
return obj.arrayBuffer();
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async function toPutBody(body) {
|
|
57
|
+
if (body instanceof Uint8Array)
|
|
58
|
+
return body;
|
|
59
|
+
if (body instanceof ArrayBuffer)
|
|
60
|
+
return body;
|
|
61
|
+
return body;
|
|
62
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { StorageProvider } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Fetch shape used by the S3 provider. Matches the global `fetch` and
|
|
4
|
+
* Cloudflare Workers `fetch`. Tests can stub this.
|
|
5
|
+
*/
|
|
6
|
+
export type S3Fetch = (input: string, init: {
|
|
7
|
+
method: string;
|
|
8
|
+
headers: Record<string, string>;
|
|
9
|
+
body?: Uint8Array;
|
|
10
|
+
}) => Promise<{
|
|
11
|
+
ok: boolean;
|
|
12
|
+
status: number;
|
|
13
|
+
arrayBuffer: () => Promise<ArrayBuffer>;
|
|
14
|
+
text: () => Promise<string>;
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* Options for {@link createS3Provider}.
|
|
18
|
+
*/
|
|
19
|
+
export interface S3ProviderOptions {
|
|
20
|
+
/** AWS access key id. */
|
|
21
|
+
accessKeyId: string;
|
|
22
|
+
/** AWS secret access key. */
|
|
23
|
+
secretAccessKey: string;
|
|
24
|
+
/** Optional session token for temporary credentials. */
|
|
25
|
+
sessionToken?: string;
|
|
26
|
+
/** S3 region (e.g. `"us-east-1"`). */
|
|
27
|
+
region: string;
|
|
28
|
+
/** S3 bucket name. */
|
|
29
|
+
bucket: string;
|
|
30
|
+
/**
|
|
31
|
+
* Endpoint URL override. Defaults to the public AWS S3 endpoint for the
|
|
32
|
+
* region (`https://s3.<region>.amazonaws.com`). Set this for S3-compatible
|
|
33
|
+
* services (MinIO, Backblaze B2, DigitalOcean Spaces, Wasabi, R2 S3 API).
|
|
34
|
+
*/
|
|
35
|
+
endpoint?: string;
|
|
36
|
+
/**
|
|
37
|
+
* When `true`, put the bucket in the URL path rather than the hostname
|
|
38
|
+
* subdomain. Defaults to `true` to stay compatible with the widest set
|
|
39
|
+
* of S3-compatible services. Set to `false` to use
|
|
40
|
+
* `https://<bucket>.s3.<region>.amazonaws.com` virtual-hosted style.
|
|
41
|
+
*/
|
|
42
|
+
forcePathStyle?: boolean;
|
|
43
|
+
/** Base URL used for the public `url` field returned from `upload`. */
|
|
44
|
+
publicBaseUrl?: string;
|
|
45
|
+
/** Override `fetch` (e.g. in tests). Defaults to global `fetch`. */
|
|
46
|
+
fetch?: S3Fetch;
|
|
47
|
+
/** Provider name (defaults to `"s3"`). */
|
|
48
|
+
name?: string;
|
|
49
|
+
/** Custom key generator; defaults to `crypto.randomUUID()`. */
|
|
50
|
+
generateKey?: () => string;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create an S3 / S3-compatible storage provider. Uses Web Crypto to sign
|
|
54
|
+
* requests with AWS SigV4, so no AWS SDK dependency is required.
|
|
55
|
+
*/
|
|
56
|
+
export declare function createS3Provider(options: S3ProviderOptions): StorageProvider;
|
|
57
|
+
//# sourceMappingURL=s3.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../src/providers/s3.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAiB,eAAe,EAAoC,MAAM,aAAa,CAAA;AAEnG;;;GAGG;AACH,MAAM,MAAM,OAAO,GAAG,CACpB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE;IACJ,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC/B,IAAI,CAAC,EAAE,UAAU,CAAA;CAClB,KACE,OAAO,CAAC;IACX,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAA;IACvC,IAAI,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAA;CAC5B,CAAC,CAAA;AAMF;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,yBAAyB;IACzB,WAAW,EAAE,MAAM,CAAA;IACnB,6BAA6B;IAC7B,eAAe,EAAE,MAAM,CAAA;IACvB,wDAAwD;IACxD,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,uEAAuE;IACvE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,oEAAoE;IACpE,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,0CAA0C;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,+DAA+D;IAC/D,WAAW,CAAC,EAAE,MAAM,MAAM,CAAA;CAC3B;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,eAAe,CAsH5E"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { presignUrl, signRequest } from "../lib/sigv4.js";
|
|
2
|
+
function defaultS3Fetch(input, init) {
|
|
3
|
+
return globalThis.fetch(input, { ...init, body: init.body });
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Create an S3 / S3-compatible storage provider. Uses Web Crypto to sign
|
|
7
|
+
* requests with AWS SigV4, so no AWS SDK dependency is required.
|
|
8
|
+
*/
|
|
9
|
+
export function createS3Provider(options) {
|
|
10
|
+
const name = options.name ?? "s3";
|
|
11
|
+
const forcePathStyle = options.forcePathStyle ?? true;
|
|
12
|
+
const endpoint = options.endpoint ??
|
|
13
|
+
(forcePathStyle
|
|
14
|
+
? `https://s3.${options.region}.amazonaws.com`
|
|
15
|
+
: `https://${options.bucket}.s3.${options.region}.amazonaws.com`);
|
|
16
|
+
const publicBaseUrl = options.publicBaseUrl ?? "";
|
|
17
|
+
const fetchImpl = options.fetch ?? defaultS3Fetch;
|
|
18
|
+
const credentials = {
|
|
19
|
+
accessKeyId: options.accessKeyId,
|
|
20
|
+
secretAccessKey: options.secretAccessKey,
|
|
21
|
+
};
|
|
22
|
+
if (options.sessionToken !== undefined)
|
|
23
|
+
credentials.sessionToken = options.sessionToken;
|
|
24
|
+
const generateKey = options.generateKey ??
|
|
25
|
+
(() => {
|
|
26
|
+
const g = globalThis;
|
|
27
|
+
return g.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
28
|
+
});
|
|
29
|
+
function buildUrl(key) {
|
|
30
|
+
if (forcePathStyle) {
|
|
31
|
+
return `${endpoint}/${encodeURIComponent(options.bucket)}/${encodeKey(key)}`;
|
|
32
|
+
}
|
|
33
|
+
return `${endpoint}/${encodeKey(key)}`;
|
|
34
|
+
}
|
|
35
|
+
async function upload(body, opts = {}) {
|
|
36
|
+
if (!fetchImpl)
|
|
37
|
+
throw new Error("S3 provider requires a fetch implementation");
|
|
38
|
+
const key = opts.key ?? generateKey();
|
|
39
|
+
const bytes = await toBytes(body);
|
|
40
|
+
const url = buildUrl(key);
|
|
41
|
+
const headers = {};
|
|
42
|
+
if (opts.contentType)
|
|
43
|
+
headers["content-type"] = opts.contentType;
|
|
44
|
+
if (opts.metadata) {
|
|
45
|
+
for (const [k, v] of Object.entries(opts.metadata)) {
|
|
46
|
+
headers[`x-amz-meta-${k.toLowerCase()}`] = v;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const signed = await signRequest({
|
|
50
|
+
method: "PUT",
|
|
51
|
+
url,
|
|
52
|
+
headers,
|
|
53
|
+
body: bytes,
|
|
54
|
+
credentials,
|
|
55
|
+
region: options.region,
|
|
56
|
+
service: "s3",
|
|
57
|
+
});
|
|
58
|
+
const response = await fetchImpl(url, {
|
|
59
|
+
method: "PUT",
|
|
60
|
+
headers: signed.headers,
|
|
61
|
+
body: bytes,
|
|
62
|
+
});
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
const text = await response.text().catch(() => "");
|
|
65
|
+
throw new Error(`S3 upload failed (${response.status}): ${text}`);
|
|
66
|
+
}
|
|
67
|
+
return { key, url: publicBaseUrl ? `${publicBaseUrl}${key}` : "" };
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
name,
|
|
71
|
+
upload,
|
|
72
|
+
async delete(key) {
|
|
73
|
+
if (!fetchImpl)
|
|
74
|
+
throw new Error("S3 provider requires a fetch implementation");
|
|
75
|
+
const url = buildUrl(key);
|
|
76
|
+
const signed = await signRequest({
|
|
77
|
+
method: "DELETE",
|
|
78
|
+
url,
|
|
79
|
+
credentials,
|
|
80
|
+
region: options.region,
|
|
81
|
+
service: "s3",
|
|
82
|
+
});
|
|
83
|
+
const response = await fetchImpl(url, {
|
|
84
|
+
method: "DELETE",
|
|
85
|
+
headers: signed.headers,
|
|
86
|
+
});
|
|
87
|
+
// S3 returns 204 on successful delete, 404 on missing — treat both as success.
|
|
88
|
+
if (!response.ok && response.status !== 404) {
|
|
89
|
+
const text = await response.text().catch(() => "");
|
|
90
|
+
throw new Error(`S3 delete failed (${response.status}): ${text}`);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
async signedUrl(key, expiresIn) {
|
|
94
|
+
return presignUrl({
|
|
95
|
+
method: "GET",
|
|
96
|
+
url: buildUrl(key),
|
|
97
|
+
expiresIn,
|
|
98
|
+
credentials,
|
|
99
|
+
region: options.region,
|
|
100
|
+
service: "s3",
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
async get(key) {
|
|
104
|
+
if (!fetchImpl)
|
|
105
|
+
throw new Error("S3 provider requires a fetch implementation");
|
|
106
|
+
const url = buildUrl(key);
|
|
107
|
+
const signed = await signRequest({
|
|
108
|
+
method: "GET",
|
|
109
|
+
url,
|
|
110
|
+
credentials,
|
|
111
|
+
region: options.region,
|
|
112
|
+
service: "s3",
|
|
113
|
+
});
|
|
114
|
+
const response = await fetchImpl(url, {
|
|
115
|
+
method: "GET",
|
|
116
|
+
headers: signed.headers,
|
|
117
|
+
});
|
|
118
|
+
if (response.status === 404)
|
|
119
|
+
return null;
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
const text = await response.text().catch(() => "");
|
|
122
|
+
throw new Error(`S3 get failed (${response.status}): ${text}`);
|
|
123
|
+
}
|
|
124
|
+
return response.arrayBuffer();
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function encodeKey(key) {
|
|
129
|
+
return key
|
|
130
|
+
.split("/")
|
|
131
|
+
.map((segment) => encodeURIComponent(segment).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`))
|
|
132
|
+
.join("/");
|
|
133
|
+
}
|
|
134
|
+
async function toBytes(body) {
|
|
135
|
+
if (body instanceof Uint8Array)
|
|
136
|
+
return body;
|
|
137
|
+
if (body instanceof ArrayBuffer)
|
|
138
|
+
return new Uint8Array(body);
|
|
139
|
+
const buffer = await body.arrayBuffer();
|
|
140
|
+
return new Uint8Array(buffer);
|
|
141
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { StorageProvider } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Thrown when a storage operation cannot find the requested provider.
|
|
4
|
+
*/
|
|
5
|
+
export declare class StorageError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Convenience wrapper exposing a single provider. Most deployments use
|
|
10
|
+
* exactly one storage backend at a time; for those cases the service is
|
|
11
|
+
* just a named wrapper.
|
|
12
|
+
*/
|
|
13
|
+
export interface StorageService extends StorageProvider {
|
|
14
|
+
/** The wrapped provider. */
|
|
15
|
+
readonly provider: StorageProvider;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Create a storage service that delegates all calls to the given provider.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createStorageService(provider: StorageProvider): StorageService;
|
|
21
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD;;GAEG;AACH,qBAAa,YAAa,SAAQ,KAAK;gBACzB,OAAO,EAAE,MAAM;CAI5B;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAe,SAAQ,eAAe;IACrD,4BAA4B;IAC5B,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAA;CACnC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,eAAe,GAAG,cAAc,CAS9E"}
|
package/dist/service.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thrown when a storage operation cannot find the requested provider.
|
|
3
|
+
*/
|
|
4
|
+
export class StorageError extends Error {
|
|
5
|
+
constructor(message) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "StorageError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Create a storage service that delegates all calls to the given provider.
|
|
12
|
+
*/
|
|
13
|
+
export function createStorageService(provider) {
|
|
14
|
+
return {
|
|
15
|
+
provider,
|
|
16
|
+
name: provider.name,
|
|
17
|
+
upload: provider.upload.bind(provider),
|
|
18
|
+
delete: provider.delete.bind(provider),
|
|
19
|
+
signedUrl: provider.signedUrl.bind(provider),
|
|
20
|
+
get: provider.get.bind(provider),
|
|
21
|
+
};
|
|
22
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accepted body shapes for uploads. Providers normalize to `Uint8Array`
|
|
3
|
+
* or pass through to their native API.
|
|
4
|
+
*/
|
|
5
|
+
export type StorageUploadBody = ArrayBuffer | Uint8Array | Blob;
|
|
6
|
+
/**
|
|
7
|
+
* Options controlling an upload.
|
|
8
|
+
*/
|
|
9
|
+
export interface UploadOptions {
|
|
10
|
+
/**
|
|
11
|
+
* Override the object key. When omitted, providers generate a random
|
|
12
|
+
* key (typically `${randomUUID()}` — UUID v4 where available).
|
|
13
|
+
*/
|
|
14
|
+
key?: string;
|
|
15
|
+
/** MIME content type (e.g. `"image/png"`). */
|
|
16
|
+
contentType?: string;
|
|
17
|
+
/** Custom metadata; persisted by providers that support it (R2, S3). */
|
|
18
|
+
metadata?: Record<string, string>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Result of a successful upload.
|
|
22
|
+
*/
|
|
23
|
+
export interface StorageObject {
|
|
24
|
+
/** Object key inside the bucket/store. */
|
|
25
|
+
key: string;
|
|
26
|
+
/**
|
|
27
|
+
* Public URL for the object when the provider exposes one. Empty string
|
|
28
|
+
* when the object is private and can only be accessed via `signedUrl`.
|
|
29
|
+
*/
|
|
30
|
+
url: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Pluggable object storage provider.
|
|
34
|
+
*
|
|
35
|
+
* Built-in implementations:
|
|
36
|
+
* - `local` — in-memory, for dev and tests
|
|
37
|
+
* - `r2` — Cloudflare R2 via the workers binding
|
|
38
|
+
* - `s3` — Amazon S3 / S3-compatible via SigV4
|
|
39
|
+
*/
|
|
40
|
+
export interface StorageProvider {
|
|
41
|
+
/** Unique provider name (e.g. `"r2"`, `"s3"`, `"local"`). */
|
|
42
|
+
readonly name: string;
|
|
43
|
+
/** Upload an object. */
|
|
44
|
+
upload(body: StorageUploadBody, options?: UploadOptions): Promise<StorageObject>;
|
|
45
|
+
/** Delete an object by key. No-op if the key does not exist. */
|
|
46
|
+
delete(key: string): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Produce a time-limited URL that grants GET access to the object.
|
|
49
|
+
* `expiresIn` is in seconds.
|
|
50
|
+
*/
|
|
51
|
+
signedUrl(key: string, expiresIn: number): Promise<string>;
|
|
52
|
+
/**
|
|
53
|
+
* Fetch an object's bytes. Returns `null` when the object is absent.
|
|
54
|
+
*/
|
|
55
|
+
get(key: string): Promise<ArrayBuffer | null>;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,MAAM,iBAAiB,GAAG,WAAW,GAAG,UAAU,GAAG,IAAI,CAAA;AAE/D;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,0CAA0C;IAC1C,GAAG,EAAE,MAAM,CAAA;IACX;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAA;CACZ;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,eAAe;IAC9B,6DAA6D;IAC7D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,wBAAwB;IACxB,MAAM,CAAC,IAAI,EAAE,iBAAiB,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CAAA;IAChF,gEAAgE;IAChE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAClC;;;OAGG;IACH,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAC1D;;OAEG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;CAC9C"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@voyant-travel/storage",
|
|
3
|
+
"version": "0.104.1",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./types": {
|
|
13
|
+
"types": "./dist/types.d.ts",
|
|
14
|
+
"import": "./dist/types.js",
|
|
15
|
+
"default": "./dist/types.js"
|
|
16
|
+
},
|
|
17
|
+
"./service": {
|
|
18
|
+
"types": "./dist/service.d.ts",
|
|
19
|
+
"import": "./dist/service.js",
|
|
20
|
+
"default": "./dist/service.js"
|
|
21
|
+
},
|
|
22
|
+
"./providers/local": {
|
|
23
|
+
"types": "./dist/providers/local.d.ts",
|
|
24
|
+
"import": "./dist/providers/local.js",
|
|
25
|
+
"default": "./dist/providers/local.js"
|
|
26
|
+
},
|
|
27
|
+
"./providers/r2": {
|
|
28
|
+
"types": "./dist/providers/r2.d.ts",
|
|
29
|
+
"import": "./dist/providers/r2.js",
|
|
30
|
+
"default": "./dist/providers/r2.js"
|
|
31
|
+
},
|
|
32
|
+
"./providers/s3": {
|
|
33
|
+
"types": "./dist/providers/s3.d.ts",
|
|
34
|
+
"import": "./dist/providers/s3.js",
|
|
35
|
+
"default": "./dist/providers/s3.js"
|
|
36
|
+
},
|
|
37
|
+
"./lib/sigv4": {
|
|
38
|
+
"types": "./dist/lib/sigv4.d.ts",
|
|
39
|
+
"import": "./dist/lib/sigv4.js",
|
|
40
|
+
"default": "./dist/lib/sigv4.js"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"dist"
|
|
45
|
+
],
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"typescript": "^6.0.2",
|
|
52
|
+
"vitest": "^4.1.2",
|
|
53
|
+
"@voyant-travel/voyant-typescript-config": "^0.1.0"
|
|
54
|
+
},
|
|
55
|
+
"repository": {
|
|
56
|
+
"type": "git",
|
|
57
|
+
"url": "https://github.com/voyant-travel/voyant.git",
|
|
58
|
+
"directory": "packages/storage"
|
|
59
|
+
},
|
|
60
|
+
"scripts": {
|
|
61
|
+
"typecheck": "tsc --noEmit",
|
|
62
|
+
"lint": "biome check src/",
|
|
63
|
+
"test": "vitest run",
|
|
64
|
+
"build": "tsc -p tsconfig.json",
|
|
65
|
+
"clean": "rm -rf dist tsconfig.tsbuildinfo"
|
|
66
|
+
},
|
|
67
|
+
"main": "./dist/index.js",
|
|
68
|
+
"types": "./dist/index.d.ts"
|
|
69
|
+
}
|