diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..50d08d0 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +npm \ No newline at end of file diff --git a/build_npm.ts b/build_npm.ts new file mode 100644 index 0000000..4ec655b --- /dev/null +++ b/build_npm.ts @@ -0,0 +1,70 @@ +import { build, emptyDir } from "https://deno.land/x/dnt@0.40.0/mod.ts"; + +const version = Deno.args[0]; + +if (!version) { + throw new Error("Please specify a version."); +} + +await emptyDir("./npm"); + +await build({ + entryPoints: ["./mod.ts"], // Replace with your actual entry point + outDir: "./npm", + testPattern: "**/*(*.test|integration).{ts,tsx,js,mjs,jsx}", + shims: { + deno: { + test: "dev", + }, + }, + compilerOptions: { + lib: ["ESNext", "DOM"], + }, + mappings: { + "node:stream/web": { + name: "node:stream/web", + }, + }, + package: { + name: "s3-lite-client", + version: version, + description: "This is a lightweight S3 client for Node.js and Deno.", + license: "MIT", + repository: { + type: "git", + url: "git+https://github.com/bradenmacdonald/deno-s3-lite-client.git", + }, + bugs: { + url: "https://github.com/bradenmacdonald/deno-s3-lite-client/issues", + }, + engines: { + "node": ">=20", + }, + author: { + "name": "Braden MacDonald", + "url": "https://github.com/bradenmacdonald", + }, + contributors: [ + "Martin Donadieu (https://martin.solos.ventures/)", + ], + devDependencies: { + "@types/node": "^20.11.1", + }, + keywords: [ + "api", + "lite", + "amazon", + "minio", + "cloud", + "s3", + "storage", + ], + }, + postBuild() { + // Copy additional files to the npm directory if needed + Deno.copyFileSync("LICENSE", "npm/LICENSE"); + Deno.copyFileSync("README.md", "npm/README.md"); + }, +}); + +console.log("Build complete. Run `cd npm && npm publish && cd ..`."); diff --git a/client.ts b/client.ts index 8f50b34..426beff 100644 --- a/client.ts +++ b/client.ts @@ -648,10 +648,20 @@ export class Client { if (typeof streamOrData === "string") { // Convert to binary using UTF-8 const binaryData = new TextEncoder().encode(streamOrData); - stream = ReadableStream.from([binaryData]); + stream = new ReadableStream({ + start(controller) { + controller.enqueue(binaryData); + controller.close(); + }, + }); size = binaryData.length; } else if (streamOrData instanceof Uint8Array) { - stream = ReadableStream.from([streamOrData]); + stream = new ReadableStream({ + start(controller) { + controller.enqueue(streamOrData); + controller.close(); + }, + }); size = streamOrData.byteLength; } else if (streamOrData instanceof ReadableStream) { stream = streamOrData; diff --git a/deno.jsonc b/deno.jsonc index ba454f4..693c37c 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -1,6 +1,15 @@ { "fmt": { - "lineWidth": 120 + "lineWidth": 120, + "exclude": ["npm/"] }, - "lock": false + "lint": { + "exclude": ["npm/"] + }, + "lock": false, + "tasks": { + "test-integration": "deno test --allow-net integration.ts", + "npm-build": "deno run -A --no-check build_npm.ts", + "npm-publish": "cd npm && npm publish && cd .." + } } diff --git a/integration.ts b/integration.ts index 63eb0e9..714f35f 100644 --- a/integration.ts +++ b/integration.ts @@ -91,11 +91,14 @@ Deno.test({ name: "putObject() can stream a large file upload", fn: async () => { // First generate a 32MiB file in memory, 1 MiB at a time, as a stream - const dataStream = ReadableStream.from(async function* () { - for (let i = 0; i < 32; i++) { - yield new Uint8Array(1024 * 1024).fill(i % 256); // Yield 1MB of data - } - }()); + const dataStream = new ReadableStream({ + start(controller) { + for (let i = 0; i < 32; i++) { + controller.enqueue(new Uint8Array(1024 * 1024).fill(i % 256)); // Yield 1MB of data + } + controller.close(); + }, + }); // Upload the 32MB stream data as 7 5MB parts. The client doesn't know in advance how big the stream is. const key = "test-32m.dat"; diff --git a/mod.ts b/mod.ts index 4f8deca..0973cb8 100644 --- a/mod.ts +++ b/mod.ts @@ -1,2 +1,4 @@ +import "./node_shims.ts"; + export { Client as S3Client } from "./client.ts"; export * as S3Errors from "./errors.ts"; diff --git a/node_shims.ts b/node_shims.ts new file mode 100644 index 0000000..a2b35d8 --- /dev/null +++ b/node_shims.ts @@ -0,0 +1,25 @@ +if (!("ReadableStream" in globalThis) || !("TransformStream" in globalThis) || !("WritableStream" in globalThis)) { + (async () => { + const { ReadableStream, TransformStream, WritableStream } = await import("node:stream/web"); + Object.defineProperties(globalThis, { + "ReadableStream": { + value: ReadableStream, + writable: true, + enumerable: false, + configurable: true, + }, + "TransformStream": { + value: TransformStream, + writable: true, + enumerable: false, + configurable: true, + }, + "WritableStream": { + value: WritableStream, + writable: true, + enumerable: false, + configurable: true, + }, + }); + })(); +} diff --git a/signing.ts b/signing.ts index cc58695..ca76c90 100644 --- a/signing.ts +++ b/signing.ts @@ -153,13 +153,12 @@ function getHeadersToSign(headers: Headers): string[] { "content-type", "user-agent", ]; - const headersToSign = []; - for (const key of headers.keys()) { - if (ignoredHeaders.includes(key.toLowerCase())) { - continue; // Ignore this header + const headersToSign: string[] = []; + headers.forEach((_value, key) => { + if (!ignoredHeaders.includes(key.toLowerCase())) { + headersToSign.push(key); } - headersToSign.push(key); - } + }); headersToSign.sort(); return headersToSign; } diff --git a/transform-chunk-sizes.test.ts b/transform-chunk-sizes.test.ts index 935db31..b55e1bf 100644 --- a/transform-chunk-sizes.test.ts +++ b/transform-chunk-sizes.test.ts @@ -9,7 +9,7 @@ import { TransformChunkSizes } from "./transform-chunk-sizes.ts"; */ class NumberSource extends ReadableStream { constructor(delayMs: number, chunksCount: number, bytesPerChunk = 1) { - let intervalTimer: number; + let intervalTimer: ReturnType; let i = 0; super({ start(controller) {