Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

release: 0.1.0-alpha.3 #14

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
676a868
chore(internal): remove unused method (#13)
stainless-app[bot] Feb 13, 2025
a0b504c
chore(internal): codegen related update (#16)
stainless-app[bot] Feb 13, 2025
c41e942
chore(internal): update eslint config (#17)
stainless-app[bot] Feb 14, 2025
7d38d98
fix(client): fix export map for index exports, accept BunFile (#18)
stainless-app[bot] Feb 14, 2025
7f7be64
chore(internal): fix tests not always being type checked (#19)
stainless-app[bot] Feb 18, 2025
4818115
feat(client): improve logging (#20)
stainless-app[bot] Feb 22, 2025
bdccc24
chore(internal): fix devcontainers setup (#21)
stainless-app[bot] Feb 22, 2025
50b5b64
fix(internal): return in castToError instead of throwing (#22)
stainless-app[bot] Feb 22, 2025
43587fa
chore(internal): remove unnecessary todo (#23)
stainless-app[bot] Feb 22, 2025
8e0ab12
docs: update URLs from stainlessapi.com to stainless.com (#24)
stainless-app[bot] Feb 28, 2025
c5e8df8
chore(client): only accept standard types for file uploads (#25)
stainless-app[bot] Mar 4, 2025
ca13c87
chore(internal): fix tests failing on node v18 (#26)
stainless-app[bot] Mar 4, 2025
c5d034a
chore(internal): constrain synckit dev dependency (#27)
stainless-app[bot] Mar 4, 2025
2bc2cb6
fix(client): fix TypeError with undefined File (#28)
stainless-app[bot] Mar 4, 2025
313a6f7
fix(internal): clean up undefined File test (#29)
stainless-app[bot] Mar 4, 2025
8211340
fix(tests): manually reset node:buffer File (#30)
stainless-app[bot] Mar 4, 2025
4d59d92
chore(types): improved go to definition on fetchOptions (#31)
stainless-app[bot] Mar 5, 2025
71c7c33
chore(docs): improve docs for withResponse/asResponse (#32)
stainless-app[bot] Mar 5, 2025
a47189e
release: 0.1.0-alpha.3
stainless-app[bot] Mar 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
chore(client): only accept standard types for file uploads (#25)
  • Loading branch information
stainless-app[bot] committed Mar 4, 2025
commit c5e8df8ef19ba385e5e286daa12a2260c9730872
4 changes: 2 additions & 2 deletions scripts/build
Original file line number Diff line number Diff line change
@@ -40,8 +40,8 @@ cp dist/index.d.ts dist/index.d.mts
cp tsconfig.dist-src.json dist/src/tsconfig.json
cp src/internal/shim-types.d.ts dist/internal/shim-types.d.ts
cp src/internal/shim-types.d.ts dist/internal/shim-types.d.mts
mkdir -p dist/internal/polyfill
cp src/internal/polyfill/*.{mjs,js,d.ts} dist/internal/polyfill
mkdir -p dist/internal/shims
cp src/internal/shims/*.{mjs,js,d.ts} dist/internal/shims

node scripts/utils/postprocess-files.cjs

4 changes: 3 additions & 1 deletion src/client.ts
Original file line number Diff line number Diff line change
@@ -472,7 +472,9 @@ export class Lightswitch {

const timeout = setTimeout(() => controller.abort(), ms);

const isReadableBody = Shims.isReadableLike(options.body);
const isReadableBody =
((globalThis as any).ReadableStream && options.body instanceof (globalThis as any).ReadableStream) ||
(typeof options.body === 'object' && options.body !== null && Symbol.asyncIterator in options.body);

const fetchOptions: RequestInit = {
signal: controller.signal as any,
9 changes: 0 additions & 9 deletions src/internal/polyfill/file.node.d.ts

This file was deleted.

17 changes: 0 additions & 17 deletions src/internal/polyfill/file.node.js

This file was deleted.

9 changes: 0 additions & 9 deletions src/internal/polyfill/file.node.mjs

This file was deleted.

56 changes: 0 additions & 56 deletions src/internal/shims.ts
Original file line number Diff line number Diff line change
@@ -20,62 +20,6 @@ export function getDefaultFetch(): Fetch {
);
}

/**
* A minimal copy of the NodeJS `stream.Readable` class so that we can
* accept the NodeJS types in certain places, e.g. file uploads
*
* https://nodejs.org/api/stream.html#class-streamreadable
*/
export interface ReadableLike {
readable: boolean;
readonly readableEnded: boolean;
readonly readableFlowing: boolean | null;
readonly readableHighWaterMark: number;
readonly readableLength: number;
readonly readableObjectMode: boolean;
destroyed: boolean;
read(size?: number): any;
pause(): this;
resume(): this;
isPaused(): boolean;
destroy(error?: Error): this;
[Symbol.asyncIterator](): AsyncIterableIterator<any>;
}

/**
* Determines if the given value looks like a NodeJS `stream.Readable`
* object and that it is readable, i.e. has not been consumed.
*
* https://nodejs.org/api/stream.html#class-streamreadable
*/
export function isReadableLike(value: any) {
// We declare our own class of Readable here, so it's not feasible to
// do an 'instanceof' check. Instead, check for Readable-like properties.
return !!value && value.readable === true && typeof value.read === 'function';
}

/**
* A minimal copy of the NodeJS `fs.ReadStream` class for usage within file uploads.
*
* https://nodejs.org/api/fs.html#class-fsreadstream
*/
export interface FsReadStreamLike extends ReadableLike {
path: {}; // real type is string | Buffer but we can't reference `Buffer` here
}

/**
* Determines if the given value looks like a NodeJS `fs.ReadStream`
* object.
*
* This just checks if the object matches our `Readable` interface
* and defines a `path` property, there may be false positives.
*
* https://nodejs.org/api/fs.html#class-fsreadstream
*/
export function isFsReadStreamLike(value: any): value is FsReadStreamLike {
return isReadableLike(value) && 'path' in value;
}

type ReadableStreamArgs = ConstructorParameters<typeof ReadableStream>;

export function makeReadableStream(...args: ReadableStreamArgs): ReadableStream {
File renamed without changes.
File renamed without changes.
File renamed without changes.
20 changes: 20 additions & 0 deletions src/internal/shims/file.node.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// The infer is to make TS show it as a nice union type,
// instead of literally `ConstructorParameters<typeof Blob>[0]`
type FallbackBlobSource = ConstructorParameters<typeof Blob>[0] extends infer T ? T : never;
/**
* A [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) provides information about files.
*/
declare class FallbackFile extends Blob {
constructor(sources: FallbackBlobSource, fileName: string, options?: any);
/**
* The name of the `File`.
*/
readonly name: string;
/**
* The last modified date of the `File`.
*/
readonly lastModified: number;
}
export type File = InstanceType<typeof File>;
export const File: typeof globalThis extends { File: infer fileConstructor } ? fileConstructor
: typeof FallbackFile;
11 changes: 11 additions & 0 deletions src/internal/shims/file.node.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
if (typeof require !== 'undefined') {
if (globalThis.File) {
exports.File = globalThis.File;
} else {
try {
// Use [require][0](...) and not require(...) so bundlers don't try to bundle the
// buffer module.
exports.File = [require][0]('node:buffer').File;
} catch (e) {}
}
}
2 changes: 2 additions & 0 deletions src/internal/shims/file.node.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import * as mod from './file.node.js';
export const File = globalThis.File || mod.File;
152 changes: 152 additions & 0 deletions src/internal/to-file.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { File } from './shims/file.node.js';
import { BlobPart, getName, makeFile, isAsyncIterable } from './uploads';
import type { FilePropertyBag } from './builtin-types';

type BlobLikePart = string | ArrayBuffer | ArrayBufferView | BlobLike | DataView;

/**
* Intended to match DOM Blob, node-fetch Blob, node:buffer Blob, etc.
* Don't add arrayBuffer here, node-fetch doesn't have it
*/
interface BlobLike {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */
readonly size: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */
readonly type: string;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */
text(): Promise<string>;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */
slice(start?: number, end?: number): BlobLike;
}

/**
* This check adds the arrayBuffer() method type because it is available and used at runtime
*/
const isBlobLike = (value: any): value is BlobLike & { arrayBuffer(): Promise<ArrayBuffer> } =>
value != null &&
typeof value === 'object' &&
typeof value.size === 'number' &&
typeof value.type === 'string' &&
typeof value.text === 'function' &&
typeof value.slice === 'function' &&
typeof value.arrayBuffer === 'function';

/**
* Intended to match DOM File, node:buffer File, undici File, etc.
*/
interface FileLike extends BlobLike {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */
readonly lastModified: number;
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */
readonly name?: string | undefined;
}

/**
* This check adds the arrayBuffer() method type because it is available and used at runtime
*/
const isFileLike = (value: any): value is FileLike & { arrayBuffer(): Promise<ArrayBuffer> } =>
value != null &&
typeof value === 'object' &&
typeof value.name === 'string' &&
typeof value.lastModified === 'number' &&
isBlobLike(value);

/**
* Intended to match DOM Response, node-fetch Response, undici Response, etc.
*/
export interface ResponseLike {
url: string;
blob(): Promise<BlobLike>;
}

const isResponseLike = (value: any): value is ResponseLike =>
value != null &&
typeof value === 'object' &&
typeof value.url === 'string' &&
typeof value.blob === 'function';

export type ToFileInput =
| FileLike
| ResponseLike
| Exclude<BlobLikePart, string>
| AsyncIterable<BlobLikePart>;

/**
* Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats
* @param value the raw content of the file. Can be an {@link Uploadable}, {@link BlobLikePart}, or {@link AsyncIterable} of {@link BlobLikePart}s
* @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible
* @param {Object=} options additional properties
* @param {string=} options.type the MIME type of the content
* @param {number=} options.lastModified the last modified timestamp
* @returns a {@link File} with the given properties
*/
export async function toFile(
value: ToFileInput | PromiseLike<ToFileInput>,
name?: string | null | undefined,
options?: FilePropertyBag | undefined,
): Promise<File> {
// If it's a promise, resolve it.
value = await value;

// If we've been given a `File` we don't need to do anything
if (isFileLike(value)) {
if (File && value instanceof File) {
return value;
}
return makeFile([await value.arrayBuffer()], value.name);
}

if (isResponseLike(value)) {
const blob = await value.blob();
name ||= new URL(value.url).pathname.split(/[\\/]/).pop();

return makeFile(await getBytes(blob), name, options);
}

const parts = await getBytes(value);

name ||= getName(value);

if (!options?.type) {
const type = parts.find((part) => typeof part === 'object' && 'type' in part && part.type);
if (typeof type === 'string') {
options = { ...options, type };
}
}

return makeFile(parts, name, options);
}

async function getBytes(value: BlobLikePart | AsyncIterable<BlobLikePart>): Promise<Array<BlobPart>> {
let parts: Array<BlobPart> = [];
if (
typeof value === 'string' ||
ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc.
value instanceof ArrayBuffer
) {
parts.push(value);
} else if (isBlobLike(value)) {
parts.push(value instanceof Blob ? value : await value.arrayBuffer());
} else if (
isAsyncIterable(value) // includes Readable, ReadableStream, etc.
) {
for await (const chunk of value) {
parts.push(...(await getBytes(chunk as BlobLikePart))); // TODO, consider validating?
}
} else {
const constructor = value?.constructor?.name;
throw new Error(
`Unexpected data type: ${typeof value}${
constructor ? `; constructor: ${constructor}` : ''
}${propsForError(value)}`,
);
}

return parts;
}

function propsForError(value: unknown): string {
if (typeof value !== 'object' || value === null) return '';
const props = Object.getOwnPropertyNames(value);
return `; props: [${props.map((p) => `"${p}"`).join(', ')}]`;
}
Loading
Oops, something went wrong.
Loading
Oops, something went wrong.