lean-s3 0.2.2 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "lean-s3",
3
3
  "author": "Niklas Mollenhauer",
4
4
  "license": "MIT",
5
- "version": "0.2.2",
5
+ "version": "0.3.0",
6
6
  "description": "A server-side S3 API for the regular user.",
7
7
  "keywords": [
8
8
  "s3",
@@ -19,15 +19,14 @@
19
19
  "types": "./dist/index.d.ts",
20
20
  "type": "module",
21
21
  "scripts": {
22
- "build": "tsgo",
23
- "clean": "rimraf dist",
22
+ "build": "tsup",
24
23
  "test": "tsgo && node --test dist/*.test.js",
25
24
  "test:integration": "tsgo && node --test dist/test.integration.js",
26
25
  "ci": "biome ci ./src",
27
26
  "docs": "typedoc",
28
27
  "lint": "biome lint ./src",
29
28
  "format": "biome format --write ./src && biome lint --write ./src && biome check --write ./src",
30
- "prepublishOnly": "npm run clean && npm run build"
29
+ "prepublishOnly": "npm run build"
31
30
  },
32
31
  "devDependencies": {
33
32
  "@biomejs/biome": "^1.9.4",
@@ -37,9 +36,8 @@
37
36
  "@typescript/native-preview": "^7.0.0-dev.20250613.1",
38
37
  "expect": "^30.0.0",
39
38
  "lefthook": "^1.11.13",
40
- "rimraf": "^6.0.1",
41
- "typedoc": "^0.28.5",
42
- "typescript": "^5.8.3"
39
+ "tsup": "^8.5.0",
40
+ "typedoc": "^0.28.5"
43
41
  },
44
42
  "engines": {
45
43
  "node": "^20.19.0 || ^22.14.0 || ^24.0.0"
package/dist/AmzDate.d.ts DELETED
@@ -1,7 +0,0 @@
1
- export type AmzDate = {
2
- numericDayStart: number;
3
- date: string;
4
- dateTime: string;
5
- };
6
- export declare function getAmzDate(dateTime: Date): AmzDate;
7
- export declare function now(): AmzDate;
package/dist/AmzDate.js DELETED
@@ -1,29 +0,0 @@
1
- const ONE_DAY = 1000 * 60 * 60 * 24;
2
- export function getAmzDate(dateTime) {
3
- const date = pad4(dateTime.getUTCFullYear()) +
4
- pad2(dateTime.getUTCMonth() + 1) +
5
- pad2(dateTime.getUTCDate());
6
- const time = pad2(dateTime.getUTCHours()) +
7
- pad2(dateTime.getUTCMinutes()) +
8
- pad2(dateTime.getUTCSeconds()); // it seems that we dont support milliseconds
9
- return {
10
- numericDayStart: (dateTime.getTime() / ONE_DAY) | 0,
11
- date,
12
- dateTime: `${date}T${time}Z`,
13
- };
14
- }
15
- export function now() {
16
- return getAmzDate(new Date());
17
- }
18
- function pad4(v) {
19
- return v < 10
20
- ? `000${v}`
21
- : v < 100
22
- ? `00${v}`
23
- : v < 1000
24
- ? `0${v}`
25
- : v.toString();
26
- }
27
- function pad2(v) {
28
- return v < 10 ? `0${v}` : v.toString();
29
- }
@@ -1,5 +0,0 @@
1
- import type { AmzDate } from "./AmzDate.ts";
2
- export default class KeyCache {
3
- #private;
4
- computeIfAbsent(date: AmzDate, region: string, accessKeyId: string, secretAccessKey: string): Buffer;
5
- }
package/dist/KeyCache.js DELETED
@@ -1,21 +0,0 @@
1
- import * as sign from "./sign.js";
2
- export default class KeyCache {
3
- #lastNumericDay = -1;
4
- #keys = new Map();
5
- computeIfAbsent(date, region, accessKeyId, secretAccessKey) {
6
- if (date.numericDayStart !== this.#lastNumericDay) {
7
- this.#keys.clear();
8
- this.#lastNumericDay = date.numericDayStart;
9
- // TODO: Add mechanism to clear the cache after some time
10
- }
11
- // using accessKeyId to prevent keeping the secretAccessKey somewhere
12
- const cacheKey = `${date.date}:${region}:${accessKeyId}`;
13
- const key = this.#keys.get(cacheKey);
14
- if (key) {
15
- return key;
16
- }
17
- const newKey = sign.deriveSigningKey(date.date, region, secretAccessKey);
18
- this.#keys.set(cacheKey, newKey);
19
- return newKey;
20
- }
21
- }
@@ -1,19 +0,0 @@
1
- import type { ChecksumType, ChecksumAlgorithm, StorageClass } from "./index.ts";
2
- /**
3
- * @internal Normally, we'd use an interface for that, but having a class with pre-defined fields makes it easier for V8 top optimize hidden classes.
4
- */
5
- export default class S3BucketEntry {
6
- readonly key: string;
7
- readonly size: number;
8
- readonly lastModified: Date;
9
- readonly etag: string;
10
- readonly storageClass: StorageClass;
11
- readonly checksumAlgorithm: ChecksumAlgorithm | undefined;
12
- readonly checksumType: ChecksumType | undefined;
13
- constructor(key: string, size: number, lastModified: Date, etag: string, storageClass: StorageClass, checksumAlgorithm: ChecksumAlgorithm | undefined, checksumType: ChecksumType | undefined);
14
- /**
15
- * @internal
16
- */
17
- // biome-ignore lint/suspicious/noExplicitAny: internal use only, any is ok here
18
- static parse(source: any): S3BucketEntry;
19
- }
@@ -1,29 +0,0 @@
1
- /**
2
- * @internal Normally, we'd use an interface for that, but having a class with pre-defined fields makes it easier for V8 top optimize hidden classes.
3
- */
4
- export default class S3BucketEntry {
5
- key;
6
- size;
7
- lastModified;
8
- etag;
9
- storageClass;
10
- checksumAlgorithm;
11
- checksumType;
12
- constructor(key, size, lastModified, etag, storageClass, checksumAlgorithm, checksumType) {
13
- this.key = key;
14
- this.size = size;
15
- this.lastModified = lastModified;
16
- this.etag = etag;
17
- this.storageClass = storageClass;
18
- this.checksumAlgorithm = checksumAlgorithm;
19
- this.checksumType = checksumType;
20
- }
21
- /**
22
- * @internal
23
- */
24
- // biome-ignore lint/suspicious/noExplicitAny: internal use only, any is ok here
25
- static parse(source) {
26
- // TODO: check values and throw exceptions
27
- return new S3BucketEntry(source.Key, source.Size, new Date(source.LastModified), source.ETag, source.StorageClass, source.ChecksumAlgorithm, source.ChecksumType);
28
- }
29
- }
@@ -1,210 +0,0 @@
1
- import { type Dispatcher } from "undici";
2
- import S3File from "./S3File.ts";
3
- import S3BucketEntry from "./S3BucketEntry.ts";
4
- import * as amzDate from "./AmzDate.ts";
5
- import type { Acl, BucketInfo, BucketLocationInfo, HttpMethod, PresignableHttpMethod, StorageClass, UndiciBodyInit } from "./index.ts";
6
- export declare const write: unique symbol;
7
- export declare const stream: unique symbol;
8
- export interface S3ClientOptions {
9
- bucket: string;
10
- region: string;
11
- endpoint: string;
12
- accessKeyId: string;
13
- secretAccessKey: string;
14
- sessionToken?: string;
15
- }
16
- export type OverridableS3ClientOptions = Pick<S3ClientOptions, "region" | "bucket" | "endpoint">;
17
- // biome-ignore lint/complexity/noBannedTypes: TODO
18
- export type CreateFileInstanceOptions = {}; // TODO
19
- export type DeleteObjectsOptions = {
20
- signal?: AbortSignal;
21
- };
22
- export interface S3FilePresignOptions {
23
- contentHash: Buffer;
24
- /** Seconds. */
25
- expiresIn: number; // TODO: Maybe support Temporal.Duration once major support arrives
26
- method: PresignableHttpMethod;
27
- storageClass: StorageClass;
28
- acl: Acl;
29
- }
30
- export type ListObjectsOptions = {
31
- bucket?: string;
32
- prefix?: string;
33
- maxKeys?: number;
34
- startAfter?: string;
35
- continuationToken?: string;
36
- signal?: AbortSignal;
37
- };
38
- export type ListObjectsIteratingOptions = {
39
- bucket?: string;
40
- prefix?: string;
41
- startAfter?: string;
42
- signal?: AbortSignal;
43
- internalPageSize?: number;
44
- };
45
- export type ListObjectsResponse = {
46
- name: string;
47
- prefix: string | undefined;
48
- startAfter: string | undefined;
49
- isTruncated: boolean;
50
- continuationToken: string | undefined;
51
- maxKeys: number;
52
- keyCount: number;
53
- nextContinuationToken: string | undefined;
54
- contents: readonly S3BucketEntry[];
55
- };
56
- export type BucketCreationOptions = {
57
- locationConstraint?: string;
58
- location?: BucketLocationInfo;
59
- info?: BucketInfo;
60
- signal?: AbortSignal;
61
- };
62
- export type BucketDeletionOptions = {
63
- signal?: AbortSignal;
64
- };
65
- export type BucketExistsOptions = {
66
- signal?: AbortSignal;
67
- };
68
- /**
69
- * A configured S3 bucket instance for managing files.
70
- *
71
- * @example
72
- * ```js
73
- * // Basic bucket setup
74
- * const bucket = new S3Client({
75
- * bucket: "my-bucket",
76
- * accessKeyId: "key",
77
- * secretAccessKey: "secret"
78
- * });
79
- * // Get file instance
80
- * const file = bucket.file("image.jpg");
81
- * await file.delete();
82
- * ```
83
- */
84
- export default class S3Client {
85
- #private;
86
- /**
87
- * Create a new instance of an S3 bucket so that credentials can be managed from a single instance instead of being passed to every method.
88
- *
89
- * @param options The default options to use for the S3 client.
90
- */
91
- constructor(options: S3ClientOptions);
92
- /**
93
- * Creates an S3File instance for the given path.
94
- *
95
- * @param {string} path The path to the object in the bucket. ALso known as [object key](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html).
96
- * We recommend not using the following characters in a key name because of significant special character handling, which isn't consistent across all applications (see [AWS docs](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-keys.html)):
97
- * - Backslash (`\\`)
98
- * - Left brace (`{`)
99
- * - Non-printable ASCII characters (128–255 decimal characters)
100
- * - Caret or circumflex (`^`)
101
- * - Right brace (`}`)
102
- * - Percent character (`%`)
103
- * - Grave accent or backtick (`\``)
104
- * - Right bracket (`]`)
105
- * - Quotation mark (`"`)
106
- * - Greater than sign (`>`)
107
- * - Left bracket (`[`)
108
- * - Tilde (`~`)
109
- * - Less than sign (`<`)
110
- * - Pound sign (`#`)
111
- * - Vertical bar or pipe (`|`)
112
- *
113
- * lean-s3 does not enforce these restrictions.
114
- *
115
- * @param {Partial<CreateFileInstanceOptions>} [options] TODO
116
- * @example
117
- * ```js
118
- * const file = client.file("image.jpg");
119
- * await file.write(imageData);
120
- *
121
- * const configFile = client.file("config.json", {
122
- * type: "application/json",
123
- * acl: "private"
124
- * });
125
- * ```
126
- */
127
- file(path: string, options?: Partial<CreateFileInstanceOptions>): S3File;
128
- /**
129
- * Generate a presigned URL for temporary access to a file.
130
- * Useful for generating upload/download URLs without exposing credentials.
131
- * @returns The operation on {@link S3Client#presign.path} as a pre-signed URL.
132
- *
133
- * @example
134
- * ```js
135
- * const downloadUrl = client.presign("file.pdf", {
136
- * expiresIn: 3600 // 1 hour
137
- * });
138
- * ```
139
- */
140
- presign(path: string, { method, expiresIn, // TODO: Maybe rename this to expiresInSeconds
141
- storageClass, acl, region: regionOverride, bucket: bucketOverride, endpoint: endpointOverride }?: Partial<S3FilePresignOptions & OverridableS3ClientOptions>): string;
142
- /**
143
- * Uses [`DeleteObjects`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html) to delete multiple objects in a single request.
144
- */
145
- deleteObjects(objects: readonly S3BucketEntry[] | readonly string[], options?: DeleteObjectsOptions): Promise<{
146
- errors: {
147
- code: any;
148
- key: any;
149
- message: any;
150
- versionId: any;
151
- }[];
152
- } | null>;
153
- /**
154
- * Creates a new bucket on the S3 server.
155
- *
156
- * @param name The name of the bucket to create. AWS the name according to [some rules](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html). The most important ones are:
157
- * - Bucket names must be between `3` (min) and `63` (max) characters long.
158
- * - Bucket names can consist only of lowercase letters, numbers, periods (`.`), and hyphens (`-`).
159
- * - Bucket names must begin and end with a letter or number.
160
- * - Bucket names must not contain two adjacent periods.
161
- * - Bucket names must not be formatted as an IP address (for example, `192.168.5.4`).
162
- *
163
- * @throws {Error} If the bucket name is invalid.
164
- * @throws {S3Error} If the bucket could not be created, e.g. if it already exists.
165
- * @remarks Uses [`CreateBucket`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html)
166
- */
167
- createBucket(name: string, options?: BucketCreationOptions): Promise<void>;
168
- /**
169
- * Deletes a bucket from the S3 server.
170
- * @param name The name of the bucket to delete. Same restrictions as in {@link S3Client#createBucket}.
171
- * @throws {Error} If the bucket name is invalid.
172
- * @throws {S3Error} If the bucket could not be deleted, e.g. if it is not empty.
173
- * @remarks Uses [`DeleteBucket`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucket.html).
174
- */
175
- deleteBucket(name: string, options?: BucketDeletionOptions): Promise<void>;
176
- /**
177
- * Checks if a bucket exists.
178
- * @param name The name of the bucket to delete. Same restrictions as in {@link S3Client#createBucket}.
179
- * @throws {Error} If the bucket name is invalid.
180
- * @remarks Uses [`HeadBucket`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadBucket.html).
181
- */
182
- bucketExists(name: string, options?: BucketExistsOptions): Promise<boolean>;
183
- //#region list
184
- /**
185
- * Uses [`ListObjectsV2`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html) to iterate over all keys. Pagination and continuation is handled internally.
186
- */
187
- listIterating(options: ListObjectsIteratingOptions): AsyncGenerator<S3BucketEntry>;
188
- /**
189
- * Implements [`ListObjectsV2`](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html) to iterate over all keys.
190
- */
191
- list(options?: ListObjectsOptions): Promise<ListObjectsResponse>;
192
- //#endregion
193
- /**
194
- * Do not use this. This is an internal method.
195
- * TODO: Maybe move this into a separate free function?
196
- * @internal
197
- */
198
- _signedRequest(method: HttpMethod, pathWithoutBucket: string, query: string | undefined, body: UndiciBodyInit | undefined, additionalSignedHeaders: Record<string, string> | undefined, additionalUnsignedHeaders: Record<string, string> | undefined, contentHash: Buffer | undefined, bucket: string | undefined, signal?: AbortSignal | undefined): Promise<Dispatcher.ResponseData<null>>;
199
- /**
200
- * @internal
201
- * @param {import("./index.d.ts").UndiciBodyInit} data TODO
202
- */
203
- [write](path: string, data: UndiciBodyInit, contentType: string, contentLength: number | undefined, contentHash: Buffer | undefined, rageStart: number | undefined, rangeEndExclusive: number | undefined, signal?: AbortSignal | undefined): Promise<void>;
204
- // TODO: Support abortSignal
205
- /**
206
- * @internal
207
- */
208
- [stream](path: string, contentHash: Buffer | undefined, rageStart: number | undefined, rangeEndExclusive: number | undefined): import("stream/web").ReadableStream<Uint8Array<ArrayBufferLike>>;
209
- }
210
- export declare function buildSearchParams(amzCredential: string, date: amzDate.AmzDate, expiresIn: number, headerList: string, contentHashStr: string | null | undefined, storageClass: StorageClass | null | undefined, sessionToken: string | null | undefined, acl: Acl | null | undefined): string;