diff options
49 files changed, 2980 insertions, 314 deletions
diff --git a/.github/workflows/bun-types-tests.yml b/.github/workflows/bun-types-tests.yml index 152a89197..a0aca829f 100644 --- a/.github/workflows/bun-types-tests.yml +++ b/.github/workflows/bun-types-tests.yml @@ -3,6 +3,7 @@ on: push: paths: - packages/bun-types/**/* + branches: [main] pull_request: paths: - packages/bun-types/**/* @@ -22,7 +23,7 @@ jobs: - name: Install bun uses: xhyrom/setup-bun@v0.1.8 with: - bun-version: canary + bun-version: latest github-token: ${{ secrets.GITHUB_TOKEN }} - name: Install node @@ -1378,6 +1378,7 @@ clean: clean-bindings (cd $(BUN_DEPS_DIR)/boringssl && make clean) || echo ""; (cd $(BUN_DEPS_DIR)/picohttp && make clean) || echo ""; (cd $(BUN_DEPS_DIR)/zlib && make clean) || echo ""; + (cd $(BUN_DEPS_DIR)/c-ares && rm -rf build && make clean) || echo ""; .PHONY: release-bindings release-bindings: $(OBJ_DIR) $(OBJ_FILES) $(WEBCORE_OBJ_FILES) $(SQLITE_OBJ_FILES) $(NODE_OS_OBJ_FILES) $(BUILTINS_OBJ_FILES) $(IO_FILES) $(MODULES_OBJ_FILES) diff --git a/packages/bun-types/buffer.d.ts b/packages/bun-types/buffer.d.ts index a7de0cfe4..5a0dd071a 100644 --- a/packages/bun-types/buffer.d.ts +++ b/packages/bun-types/buffer.d.ts @@ -44,6 +44,7 @@ * @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/buffer.js) */ declare module "buffer" { + import { ArrayBufferView } from "bun"; export const INSPECT_MAX_BYTES: number; export const kMaxLength: number; export type TranscodeEncoding = @@ -244,7 +245,7 @@ declare module "buffer" { * @return The number of bytes contained within `string`. */ byteLength( - string: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, + string: string | ArrayBufferView | ArrayBufferLike, encoding?: BufferEncoding, ): number; /** diff --git a/packages/bun-types/bun.d.ts b/packages/bun-types/bun.d.ts index 8eb664ce1..41deb9410 100644 --- a/packages/bun-types/bun.d.ts +++ b/packages/bun-types/bun.d.ts @@ -1,5 +1,3 @@ -import { Encoding } from "crypto"; - interface VoidFunction { (): void; } @@ -28,6 +26,8 @@ declare namespace Bun { * */ declare module "bun" { + type ArrayBufferView = TypedArray | DataView; + import { Encoding as CryptoEncoding } from "crypto"; /** * The environment variables of the process * @@ -1374,6 +1374,21 @@ declare module "bun" { * @see {@link ServerWebSocket.publish} */ closeOnBackpressureLimit?: boolean; + + /** + * Control whether or not ws.publish() should include the ServerWebSocket + * that published the message. This is enabled by default, but it was an API + * design mistake. A future version of Bun will change this default to + * `false` and eventually remove this option entirely. The better way to publish to all is to use {@link Server.publish}. + * + * if `true` or `undefined`, {@link ServerWebSocket.publish} will publish to all subscribers, including the websocket publishing the message. + * + * if `false`, {@link ServerWebSocket.publish} will publish to all subscribers excluding the websocket publishing the message. + * + * @default true + * + */ + publishToSelf?: boolean; } interface GenericServeOptions { @@ -2085,7 +2100,7 @@ declare module "bun" { * * @param input */ - update(input: StringOrBuffer, inputEncoding?: Encoding): CryptoHasher; + update(input: StringOrBuffer, inputEncoding?: CryptoEncoding): CryptoHasher; /** * Finalize the hash @@ -2753,6 +2768,12 @@ declare module "bun" { drain?(socket: Socket<Data>): void | Promise<void>; /** + * When the socket has been shutdown from the other end, this function is + * called. This is a TCP FIN packet. + */ + end?(socket: Socket<Data>): void | Promise<void>; + + /** * When the socket fails to be created, this function is called. * * The promise returned by `Bun.connect` rejects **after** this function is @@ -3225,6 +3246,7 @@ type TypedArray = | Uint32Array | Float32Array | Float64Array; + type TimeLike = string | number | Date; type StringOrBuffer = string | TypedArray | ArrayBufferLike; type PathLike = string | TypedArray | ArrayBufferLike | URL; diff --git a/packages/bun-types/child_process.d.ts b/packages/bun-types/child_process.d.ts index c36a7dd0d..04389986f 100644 --- a/packages/bun-types/child_process.d.ts +++ b/packages/bun-types/child_process.d.ts @@ -66,7 +66,7 @@ * @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/child_process.js) */ declare module "child_process" { - import { SpawnOptions } from "bun"; + import { SpawnOptions, ArrayBufferView } from "bun"; import { ObjectEncodingOptions } from "node:fs"; import { EventEmitter, Abortable } from "node:events"; diff --git a/packages/bun-types/crypto.d.ts b/packages/bun-types/crypto.d.ts index 288bfe916..411deebd5 100644 --- a/packages/bun-types/crypto.d.ts +++ b/packages/bun-types/crypto.d.ts @@ -16,6 +16,7 @@ * @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/crypto.js) */ declare module "crypto" { + import { ArrayBufferView } from "bun"; import * as stream from "node:stream"; /** * SPKAC is a Certificate Signing Request mechanism originally implemented by diff --git a/packages/bun-types/dns.d.ts b/packages/bun-types/dns.d.ts new file mode 100644 index 000000000..b5c9a777b --- /dev/null +++ b/packages/bun-types/dns.d.ts @@ -0,0 +1,895 @@ +/** + * The `dns` module enables name resolution. For example, use it to look up IP + * addresses of host names. + * + * Although named for the [Domain Name System (DNS)](https://en.wikipedia.org/wiki/Domain_Name_System), it does not always use the + * DNS protocol for lookups. {@link lookup} uses the operating system + * facilities to perform name resolution. It may not need to perform any network + * communication. To perform name resolution the way other applications on the same + * system do, use {@link lookup}. + * + * ```js + * const dns = require('dns'); + * + * dns.lookup('example.org', (err, address, family) => { + * console.log('address: %j family: IPv%s', address, family); + * }); + * // address: "93.184.216.34" family: IPv4 + * ``` + * + * All other functions in the `dns` module connect to an actual DNS server to + * perform name resolution. They will always use the network to perform DNS + * queries. These functions do not use the same set of configuration files used by {@link lookup} (e.g. `/etc/hosts`). Use these functions to always perform + * DNS queries, bypassing other name-resolution facilities. + * + * ```js + * const dns = require('dns'); + * + * dns.resolve4('archive.org', (err, addresses) => { + * if (err) throw err; + * + * console.log(`addresses: ${JSON.stringify(addresses)}`); + * + * addresses.forEach((a) => { + * dns.reverse(a, (err, hostnames) => { + * if (err) { + * throw err; + * } + * console.log(`reverse for ${a}: ${JSON.stringify(hostnames)}`); + * }); + * }); + * }); + * ``` + * + * See the `Implementation considerations section` for more information. + * @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/dns.js) + */ +declare module "dns" { + import * as dnsPromises from "node:dns/promises"; + // Supported getaddrinfo flags. + export const ADDRCONFIG: number; + export const V4MAPPED: number; + /** + * If `dns.V4MAPPED` is specified, return resolved IPv6 addresses as + * well as IPv4 mapped IPv6 addresses. + */ + export const ALL: number; + export interface LookupOptions { + family?: number | undefined; + // hints?: number | undefined; + // all?: boolean | undefined; + /** + * @default true + */ + // verbatim?: boolean | undefined; + } + export interface LookupOneOptions extends LookupOptions { + // all?: false | undefined; + } + export interface LookupAllOptions extends LookupOptions { + // all: true; + } + export interface LookupAddress { + address: string; + family: number; + } + /** + * Resolves a host name (e.g. `'nodejs.org'`) into the first found A (IPv4) or + * AAAA (IPv6) record. All `option` properties are optional. If `options` is an + * integer, then it must be `4` or `6` – if `options` is not provided, then IPv4 + * and IPv6 addresses are both returned if found. + * + * With the `all` option set to `true`, the arguments for `callback` change to`(err, addresses)`, with `addresses` being an array of objects with the + * properties `address` and `family`. + * + * On error, `err` is an `Error` object, where `err.code` is the error code. + * Keep in mind that `err.code` will be set to `'ENOTFOUND'` not only when + * the host name does not exist but also when the lookup fails in other ways + * such as no available file descriptors. + * + * `dns.lookup()` does not necessarily have anything to do with the DNS protocol. + * The implementation uses an operating system facility that can associate names + * with addresses, and vice versa. This implementation can have subtle but + * important consequences on the behavior of any Node.js program. Please take some + * time to consult the `Implementation considerations section` before using`dns.lookup()`. + * + * Example usage: + * + * ```js + * const dns = require('dns'); + * const options = { + * family: 6, + * hints: dns.ADDRCONFIG | dns.V4MAPPED, + * }; + * dns.lookup('example.com', options, (err, address, family) => + * console.log('address: %j family: IPv%s', address, family)); + * // address: "2606:2800:220:1:248:1893:25c8:1946" family: IPv6 + * + * // When options.all is true, the result will be an Array. + * options.all = true; + * dns.lookup('example.com', options, (err, addresses) => + * console.log('addresses: %j', addresses)); + * // addresses: [{"address":"2606:2800:220:1:248:1893:25c8:1946","family":6}] + * ``` + * + * If this method is invoked as its `util.promisify()` ed version, and `all`is not set to `true`, it returns a `Promise` for an `Object` with `address` and`family` properties. + * @since v0.1.90 + */ + export function lookup( + hostname: string, + family: number, + callback: ( + err: ErrnoException | null, + address: string, + family: number, + ) => void, + ): void; + // export function lookup( + // hostname: string, + // options: LookupOneOptions, + // callback: ( + // err: ErrnoException | null, + // address: string, + // family: number, + // ) => void, + // ): void; + // export function lookup( + // hostname: string, + // options: LookupAllOptions, + // callback: ( + // err: ErrnoException | null, + // addresses: LookupAddress[], + // ) => void, + // ): void; + export function lookup( + hostname: string, + options: LookupOptions, + callback: ( + err: ErrnoException | null, + address: string | LookupAddress[], + family: number, + ) => void, + ): void; + export function lookup( + hostname: string, + callback: ( + err: ErrnoException | null, + address: string, + family: number, + ) => void, + ): void; + // export namespace lookup { + // function __promisify__( + // hostname: string, + // options: LookupAllOptions, + // ): Promise<LookupAddress[]>; + // function __promisify__( + // hostname: string, + // options?: LookupOneOptions | number, + // ): Promise<LookupAddress>; + // function __promisify__( + // hostname: string, + // options: LookupOptions, + // ): Promise<LookupAddress | LookupAddress[]>; + // } + /** + * Resolves the given `address` and `port` into a host name and service using + * the operating system's underlying `getnameinfo` implementation. + * + * If `address` is not a valid IP address, a `TypeError` will be thrown. + * The `port` will be coerced to a number. If it is not a legal port, a `TypeError`will be thrown. + * + * On an error, `err` is an `Error` object, where `err.code` is the error code. + * + * ```js + * const dns = require('dns'); + * dns.lookupService('127.0.0.1', 22, (err, hostname, service) => { + * console.log(hostname, service); + * // Prints: localhost ssh + * }); + * ``` + * + * If this method is invoked as its `util.promisify()` ed version, it returns a`Promise` for an `Object` with `hostname` and `service` properties. + * @since v0.11.14 + */ + export function lookupService( + address: string, + port: number, + callback: ( + err: ErrnoException | null, + hostname: string, + service: string, + ) => void, + ): void; + // export namespace lookupService { + // function __promisify__( + // address: string, + // port: number, + // ): Promise<{ + // hostname: string; + // service: string; + // }>; + // } + export interface ResolveOptions { + ttl: boolean; + } + export interface ResolveWithTtlOptions extends ResolveOptions { + ttl: true; + } + export interface RecordWithTtl { + address: string; + ttl: number; + } + /** @deprecated Use `AnyARecord` or `AnyAaaaRecord` instead. */ + export type AnyRecordWithTtl = AnyARecord | AnyAaaaRecord; + export interface AnyARecord extends RecordWithTtl { + type: "A"; + } + export interface AnyAaaaRecord extends RecordWithTtl { + type: "AAAA"; + } + export interface CaaRecord { + critial: number; + issue?: string | undefined; + issuewild?: string | undefined; + iodef?: string | undefined; + contactemail?: string | undefined; + contactphone?: string | undefined; + } + export interface MxRecord { + priority: number; + exchange: string; + } + export interface AnyMxRecord extends MxRecord { + type: "MX"; + } + export interface NaptrRecord { + flags: string; + service: string; + regexp: string; + replacement: string; + order: number; + preference: number; + } + export interface AnyNaptrRecord extends NaptrRecord { + type: "NAPTR"; + } + export interface SoaRecord { + nsname: string; + hostmaster: string; + serial: number; + refresh: number; + retry: number; + expire: number; + minttl: number; + } + export interface AnySoaRecord extends SoaRecord { + type: "SOA"; + } + export interface SrvRecord { + priority: number; + weight: number; + port: number; + name: string; + } + export interface AnySrvRecord extends SrvRecord { + type: "SRV"; + } + export interface AnyTxtRecord { + type: "TXT"; + entries: string[]; + } + export interface AnyNsRecord { + type: "NS"; + value: string; + } + export interface AnyPtrRecord { + type: "PTR"; + value: string; + } + export interface AnyCnameRecord { + type: "CNAME"; + value: string; + } + export type AnyRecord = + | AnyARecord + | AnyAaaaRecord + | AnyCnameRecord + | AnyMxRecord + | AnyNaptrRecord + | AnyNsRecord + | AnyPtrRecord + | AnySoaRecord + | AnySrvRecord + | AnyTxtRecord; + /** + * Uses the DNS protocol to resolve a host name (e.g. `'nodejs.org'`) into an array + * of the resource records. The `callback` function has arguments`(err, records)`. When successful, `records` will be an array of resource + * records. The type and structure of individual results varies based on `rrtype`: + * + * <omitted> + * + * On error, `err` is an `Error` object, where `err.code` is one of the `DNS error codes`. + * @since v0.1.27 + * @param hostname Host name to resolve. + * @param [rrtype='A'] Resource record type. + */ + export function resolve( + hostname: string, + callback: (err: ErrnoException | null, addresses: string[]) => void, + ): void; + // export function resolve( + // hostname: string, + // rrtype: "A", + // callback: (err: ErrnoException | null, addresses: string[]) => void, + // ): void; + // export function resolve( + // hostname: string, + // rrtype: "AAAA", + // callback: (err: ErrnoException | null, addresses: string[]) => void, + // ): void; + // export function resolve( + // hostname: string, + // rrtype: "ANY", + // callback: ( + // err: ErrnoException | null, + // addresses: AnyRecord[], + // ) => void, + // ): void; + // export function resolve( + // hostname: string, + // rrtype: "CNAME", + // callback: (err: ErrnoException | null, addresses: string[]) => void, + // ): void; + // export function resolve( + // hostname: string, + // rrtype: "MX", + // callback: ( + // err: ErrnoException | null, + // addresses: MxRecord[], + // ) => void, + // ): void; + // export function resolve( + // hostname: string, + // rrtype: "NAPTR", + // callback: ( + // err: ErrnoException | null, + // addresses: NaptrRecord[], + // ) => void, + // ): void; + // export function resolve( + // hostname: string, + // rrtype: "NS", + // callback: (err: ErrnoException | null, addresses: string[]) => void, + // ): void; + // export function resolve( + // hostname: string, + // rrtype: "PTR", + // callback: (err: ErrnoException | null, addresses: string[]) => void, + // ): void; + // export function resolve( + // hostname: string, + // rrtype: "SOA", + // callback: (err: ErrnoException | null, addresses: SoaRecord) => void, + // ): void; + // export function resolve( + // hostname: string, + // rrtype: "SRV", + // callback: ( + // err: ErrnoException | null, + // addresses: SrvRecord[], + // ) => void, + // ): void; + // export function resolve( + // hostname: string, + // rrtype: "TXT", + // callback: ( + // err: ErrnoException | null, + // addresses: string[][], + // ) => void, + // ): void; + // export function resolve( + // hostname: string, + // rrtype: string, + // callback: ( + // err: ErrnoException | null, + // addresses: + // | string[] + // | MxRecord[] + // | NaptrRecord[] + // | SoaRecord + // | SrvRecord[] + // | string[][] + // | AnyRecord[], + // ) => void, + // ): void; + // export namespace resolve { + // function __promisify__( + // hostname: string, + // rrtype?: "A" | "AAAA" | "CNAME" | "NS" | "PTR", + // ): Promise<string[]>; + // function __promisify__( + // hostname: string, + // rrtype: "ANY", + // ): Promise<AnyRecord[]>; + // function __promisify__(hostname: string, rrtype: "MX"): Promise<MxRecord[]>; + // function __promisify__( + // hostname: string, + // rrtype: "NAPTR", + // ): Promise<NaptrRecord[]>; + // function __promisify__(hostname: string, rrtype: "SOA"): Promise<SoaRecord>; + // function __promisify__( + // hostname: string, + // rrtype: "SRV", + // ): Promise<SrvRecord[]>; + // function __promisify__( + // hostname: string, + // rrtype: "TXT", + // ): Promise<string[][]>; + // function __promisify__( + // hostname: string, + // rrtype: string, + // ): Promise< + // | string[] + // | MxRecord[] + // | NaptrRecord[] + // | SoaRecord + // | SrvRecord[] + // | string[][] + // | AnyRecord[] + // >; + // } + /** + * Uses the DNS protocol to resolve a IPv4 addresses (`A` records) for the`hostname`. The `addresses` argument passed to the `callback` function + * will contain an array of IPv4 addresses (e.g.`['74.125.79.104', '74.125.79.105', '74.125.79.106']`). + * @since v0.1.16 + * @param hostname Host name to resolve. + */ + export function resolve4( + hostname: string, + callback: (err: ErrnoException | null, addresses: string[]) => void, + ): void; + export function resolve4( + hostname: string, + options: ResolveWithTtlOptions, + callback: (err: ErrnoException | null, addresses: RecordWithTtl[]) => void, + ): void; + export function resolve4( + hostname: string, + options: ResolveOptions, + callback: ( + err: ErrnoException | null, + addresses: string[] | RecordWithTtl[], + ) => void, + ): void; + // export namespace resolve4 { + // function __promisify__(hostname: string): Promise<string[]>; + // function __promisify__( + // hostname: string, + // options: ResolveWithTtlOptions, + // ): Promise<RecordWithTtl[]>; + // function __promisify__( + // hostname: string, + // options?: ResolveOptions, + // ): Promise<string[] | RecordWithTtl[]>; + // } + /** + * Uses the DNS protocol to resolve a IPv6 addresses (`AAAA` records) for the`hostname`. The `addresses` argument passed to the `callback` function + * will contain an array of IPv6 addresses. + * @since v0.1.16 + * @param hostname Host name to resolve. + */ + export function resolve6( + hostname: string, + callback: (err: ErrnoException | null, addresses: string[]) => void, + ): void; + export function resolve6( + hostname: string, + options: ResolveWithTtlOptions, + callback: (err: ErrnoException | null, addresses: RecordWithTtl[]) => void, + ): void; + export function resolve6( + hostname: string, + options: ResolveOptions, + callback: ( + err: ErrnoException | null, + addresses: string[] | RecordWithTtl[], + ) => void, + ): void; + // export namespace resolve6 { + // function __promisify__(hostname: string): Promise<string[]>; + // function __promisify__( + // hostname: string, + // options: ResolveWithTtlOptions, + // ): Promise<RecordWithTtl[]>; + // function __promisify__( + // hostname: string, + // options?: ResolveOptions, + // ): Promise<string[] | RecordWithTtl[]>; + // } + /** + * Uses the DNS protocol to resolve `CNAME` records for the `hostname`. The`addresses` argument passed to the `callback` function + * will contain an array of canonical name records available for the `hostname`(e.g. `['bar.example.com']`). + * @since v0.3.2 + */ + // export function resolveCname( + // hostname: string, + // callback: (err: ErrnoException | null, addresses: string[]) => void, + // ): void; + // export namespace resolveCname { + // function __promisify__(hostname: string): Promise<string[]>; + // } + /** + * Uses the DNS protocol to resolve `CAA` records for the `hostname`. The`addresses` argument passed to the `callback` function + * will contain an array of certification authority authorization records + * available for the `hostname` (e.g. `[{critical: 0, iodef: 'mailto:pki@example.com'}, {critical: 128, issue: 'pki.example.com'}]`). + * @since v15.0.0, v14.17.0 + */ + // export function resolveCaa( + // hostname: string, + // callback: (err: ErrnoException | null, records: CaaRecord[]) => void, + // ): void; + // export namespace resolveCaa { + // function __promisify__(hostname: string): Promise<CaaRecord[]>; + // } + /** + * Uses the DNS protocol to resolve mail exchange records (`MX` records) for the`hostname`. The `addresses` argument passed to the `callback` function will + * contain an array of objects containing both a `priority` and `exchange`property (e.g. `[{priority: 10, exchange: 'mx.example.com'}, ...]`). + * @since v0.1.27 + */ + // export function resolveMx( + // hostname: string, + // callback: (err: ErrnoException | null, addresses: MxRecord[]) => void, + // ): void; + // export namespace resolveMx { + // function __promisify__(hostname: string): Promise<MxRecord[]>; + // } + /** + * Uses the DNS protocol to resolve regular expression based records (`NAPTR`records) for the `hostname`. The `addresses` argument passed to the `callback`function will contain an array of + * objects with the following properties: + * + * * `flags` + * * `service` + * * `regexp` + * * `replacement` + * * `order` + * * `preference` + * + * ```js + * { + * flags: 's', + * service: 'SIP+D2U', + * regexp: '', + * replacement: '_sip._udp.example.com', + * order: 30, + * preference: 100 + * } + * ``` + * @since v0.9.12 + */ + // export function resolveNaptr( + // hostname: string, + // callback: (err: ErrnoException | null, addresses: NaptrRecord[]) => void, + // ): void; + // export namespace resolveNaptr { + // function __promisify__(hostname: string): Promise<NaptrRecord[]>; + // } + /** + * Uses the DNS protocol to resolve name server records (`NS` records) for the`hostname`. The `addresses` argument passed to the `callback` function will + * contain an array of name server records available for `hostname`(e.g. `['ns1.example.com', 'ns2.example.com']`). + * @since v0.1.90 + */ + // export function resolveNs( + // hostname: string, + // callback: (err: ErrnoException | null, addresses: string[]) => void, + // ): void; + // export namespace resolveNs { + // function __promisify__(hostname: string): Promise<string[]>; + // } + /** + * Uses the DNS protocol to resolve pointer records (`PTR` records) for the`hostname`. The `addresses` argument passed to the `callback` function will + * be an array of strings containing the reply records. + * @since v6.0.0 + */ + // export function resolvePtr( + // hostname: string, + // callback: (err: ErrnoException | null, addresses: string[]) => void, + // ): void; + // export namespace resolvePtr { + // function __promisify__(hostname: string): Promise<string[]>; + // } + /** + * Uses the DNS protocol to resolve a start of authority record (`SOA` record) for + * the `hostname`. The `address` argument passed to the `callback` function will + * be an object with the following properties: + * + * * `nsname` + * * `hostmaster` + * * `serial` + * * `refresh` + * * `retry` + * * `expire` + * * `minttl` + * + * ```js + * { + * nsname: 'ns.example.com', + * hostmaster: 'root.example.com', + * serial: 2013101809, + * refresh: 10000, + * retry: 2400, + * expire: 604800, + * minttl: 3600 + * } + * ``` + * @since v0.11.10 + */ + // export function resolveSoa( + // hostname: string, + // callback: (err: ErrnoException | null, address: SoaRecord) => void, + // ): void; + // export namespace resolveSoa { + // function __promisify__(hostname: string): Promise<SoaRecord>; + // } + /** + * Uses the DNS protocol to resolve service records (`SRV` records) for the`hostname`. The `addresses` argument passed to the `callback` function will + * be an array of objects with the following properties: + * + * * `priority` + * * `weight` + * * `port` + * * `name` + * + * ```js + * { + * priority: 10, + * weight: 5, + * port: 21223, + * name: 'service.example.com' + * } + * ``` + * @since v0.1.27 + */ + // export function resolveSrv( + // hostname: string, + // callback: (err: ErrnoException | null, addresses: SrvRecord[]) => void, + // ): void; + // export namespace resolveSrv { + // function __promisify__(hostname: string): Promise<SrvRecord[]>; + // } + /** + * Uses the DNS protocol to resolve text queries (`TXT` records) for the`hostname`. The `records` argument passed to the `callback` function is a + * two-dimensional array of the text records available for `hostname` (e.g.`[ ['v=spf1 ip4:0.0.0.0 ', '~all' ] ]`). Each sub-array contains TXT chunks of + * one record. Depending on the use case, these could be either joined together or + * treated separately. + * @since v0.1.27 + */ + // export function resolveTxt( + // hostname: string, + // callback: (err: ErrnoException | null, addresses: string[][]) => void, + // ): void; + // export namespace resolveTxt { + // function __promisify__(hostname: string): Promise<string[][]>; + // } + /** + * Uses the DNS protocol to resolve all records (also known as `ANY` or `*` query). + * The `ret` argument passed to the `callback` function will be an array containing + * various types of records. Each object has a property `type` that indicates the + * type of the current record. And depending on the `type`, additional properties + * will be present on the object: + * + * <omitted> + * + * Here is an example of the `ret` object passed to the callback: + * + * ```js + * [ { type: 'A', address: '127.0.0.1', ttl: 299 }, + * { type: 'CNAME', value: 'example.com' }, + * { type: 'MX', exchange: 'alt4.aspmx.l.example.com', priority: 50 }, + * { type: 'NS', value: 'ns1.example.com' }, + * { type: 'TXT', entries: [ 'v=spf1 include:_spf.example.com ~all' ] }, + * { type: 'SOA', + * nsname: 'ns1.example.com', + * hostmaster: 'admin.example.com', + * serial: 156696742, + * refresh: 900, + * retry: 900, + * expire: 1800, + * minttl: 60 } ] + * ``` + * + * DNS server operators may choose not to respond to `ANY`queries. It may be better to call individual methods like {@link resolve4},{@link resolveMx}, and so on. For more details, see [RFC + * 8482](https://tools.ietf.org/html/rfc8482). + */ + // export function resolveAny( + // hostname: string, + // callback: (err: ErrnoException | null, addresses: AnyRecord[]) => void, + // ): void; + // export namespace resolveAny { + // function __promisify__(hostname: string): Promise<AnyRecord[]>; + // } + /** + * Performs a reverse DNS query that resolves an IPv4 or IPv6 address to an + * array of host names. + * + * On error, `err` is an `Error` object, where `err.code` is + * one of the `DNS error codes`. + * @since v0.1.16 + */ + // export function reverse( + // ip: string, + // callback: (err: ErrnoException | null, hostnames: string[]) => void, + // ): void; + /** + * Sets the IP address and port of servers to be used when performing DNS + * resolution. The `servers` argument is an array of [RFC 5952](https://tools.ietf.org/html/rfc5952#section-6) formatted + * addresses. If the port is the IANA default DNS port (53) it can be omitted. + * + * ```js + * dns.setServers([ + * '4.4.4.4', + * '[2001:4860:4860::8888]', + * '4.4.4.4:1053', + * '[2001:4860:4860::8888]:1053', + * ]); + * ``` + * + * An error will be thrown if an invalid address is provided. + * + * The `dns.setServers()` method must not be called while a DNS query is in + * progress. + * + * The {@link setServers} method affects only {@link resolve},`dns.resolve*()` and {@link reverse} (and specifically _not_ {@link lookup}). + * + * This method works much like [resolve.conf](https://man7.org/linux/man-pages/man5/resolv.conf.5.html). + * That is, if attempting to resolve with the first server provided results in a`NOTFOUND` error, the `resolve()` method will _not_ attempt to resolve with + * subsequent servers provided. Fallback DNS servers will only be used if the + * earlier ones time out or result in some other error. + * @since v0.11.3 + * @param servers array of `RFC 5952` formatted addresses + */ + // export function setServers(servers: ReadonlyArray<string>): void; + /** + * Returns an array of IP address strings, formatted according to [RFC 5952](https://tools.ietf.org/html/rfc5952#section-6), + * that are currently configured for DNS resolution. A string will include a port + * section if a custom port is used. + * + * ```js + * [ + * '4.4.4.4', + * '2001:4860:4860::8888', + * '4.4.4.4:1053', + * '[2001:4860:4860::8888]:1053', + * ] + * ``` + * @since v0.11.3 + */ + // export function getServers(): string[]; + /** + * Set the default value of `verbatim` in {@link lookup} and `dnsPromises.lookup()`. The value could be: + * + * * `ipv4first`: sets default `verbatim` `false`. + * * `verbatim`: sets default `verbatim` `true`. + * + * The default is `ipv4first` and {@link setDefaultResultOrder} have higher + * priority than `--dns-result-order`. When using `worker threads`,{@link setDefaultResultOrder} from the main thread won't affect the default + * dns orders in workers. + * @since v16.4.0, v14.18.0 + * @param order must be `'ipv4first'` or `'verbatim'`. + */ + // export function setDefaultResultOrder(order: "ipv4first" | "verbatim"): void; + // Error codes + export const NODATA: string; + export const FORMERR: string; + export const SERVFAIL: string; + export const NOTFOUND: string; + export const NOTIMP: string; + export const REFUSED: string; + export const BADQUERY: string; + export const BADNAME: string; + export const BADFAMILY: string; + export const BADRESP: string; + export const CONNREFUSED: string; + export const TIMEOUT: string; + export const EOF: string; + export const FILE: string; + export const NOMEM: string; + export const DESTRUCTION: string; + export const BADSTR: string; + export const BADFLAGS: string; + export const NONAME: string; + export const BADHINTS: string; + export const NOTINITIALIZED: string; + export const LOADIPHLPAPI: string; + export const ADDRGETNETWORKPARAMS: string; + export const CANCELLED: string; + export interface ResolverOptions { + timeout?: number | undefined; + /** + * @default 4 + */ + tries?: number; + } + /** + * An independent resolver for DNS requests. + * + * Creating a new resolver uses the default server settings. Setting + * the servers used for a resolver using `resolver.setServers()` does not affect + * other resolvers: + * + * ```js + * const { Resolver } = require('dns'); + * const resolver = new Resolver(); + * resolver.setServers(['4.4.4.4']); + * + * // This request will use the server at 4.4.4.4, independent of global settings. + * resolver.resolve4('example.org', (err, addresses) => { + * // ... + * }); + * ``` + * + * The following methods from the `dns` module are available: + * + * * `resolver.getServers()` + * * `resolver.resolve()` + * * `resolver.resolve4()` + * * `resolver.resolve6()` + * * `resolver.resolveAny()` + * * `resolver.resolveCaa()` + * * `resolver.resolveCname()` + * * `resolver.resolveMx()` + * * `resolver.resolveNaptr()` + * * `resolver.resolveNs()` + * * `resolver.resolvePtr()` + * * `resolver.resolveSoa()` + * * `resolver.resolveSrv()` + * * `resolver.resolveTxt()` + * * `resolver.reverse()` + * * `resolver.setServers()` + * @since v8.3.0 + */ + export class Resolver { + constructor(options?: ResolverOptions); + /** + * Cancel all outstanding DNS queries made by this resolver. The corresponding + * callbacks will be called with an error with code `ECANCELLED`. + * @since v8.3.0 + */ + cancel(): void; + // getServers: typeof getServers; + resolve: typeof resolve; + resolve4: typeof resolve4; + resolve6: typeof resolve6; + // resolveAny: typeof resolveAny; + // resolveCname: typeof resolveCname; + // resolveMx: typeof resolveMx; + // resolveNaptr: typeof resolveNaptr; + // resolveNs: typeof resolveNs; + // resolvePtr: typeof resolvePtr; + // resolveSoa: typeof resolveSoa; + // resolveSrv: typeof resolveSrv; + // resolveTxt: typeof resolveTxt; + // reverse: typeof reverse; + /** + * The resolver instance will send its requests from the specified IP address. + * This allows programs to specify outbound interfaces when used on multi-homed + * systems. + * + * If a v4 or v6 address is not specified, it is set to the default, and the + * operating system will choose a local address automatically. + * + * The resolver will use the v4 local address when making requests to IPv4 DNS + * servers, and the v6 local address when making requests to IPv6 DNS servers. + * The `rrtype` of resolution requests has no impact on the local address used. + * @since v15.1.0, v14.17.0 + * @param [ipv4='0.0.0.0'] A string representation of an IPv4 address. + * @param [ipv6='::0'] A string representation of an IPv6 address. + */ + // setLocalAddress(ipv4?: string, ipv6?: string): void; + // setServers: typeof setServers; + } + export { dnsPromises as promises }; +} +declare module "node:dns" { + export * from "dns"; +} diff --git a/packages/bun-types/dns/promises.d.ts b/packages/bun-types/dns/promises.d.ts new file mode 100644 index 000000000..6460128b2 --- /dev/null +++ b/packages/bun-types/dns/promises.d.ts @@ -0,0 +1,402 @@ +/** + * The `dns.promises` API provides an alternative set of asynchronous DNS methods + * that return `Promise` objects rather than using callbacks. The API is accessible + * via `require('dns').promises` or `require('dns/promises')`. + * @since v10.6.0 + */ +declare module "dns/promises" { + import { + LookupAddress, + LookupOneOptions, + LookupAllOptions, + LookupOptions, + // AnyRecord, + // CaaRecord, + // MxRecord, + // NaptrRecord, + // SoaRecord, + // SrvRecord, + ResolveWithTtlOptions, + RecordWithTtl, + ResolveOptions, + ResolverOptions, + } from "node:dns"; + /** + * Returns an array of IP address strings, formatted according to [RFC 5952](https://tools.ietf.org/html/rfc5952#section-6), + * that are currently configured for DNS resolution. A string will include a port + * section if a custom port is used. + * + * ```js + * [ + * '4.4.4.4', + * '2001:4860:4860::8888', + * '4.4.4.4:1053', + * '[2001:4860:4860::8888]:1053', + * ] + * ``` + * @since v10.6.0 + */ + // function getServers(): string[]; + /** + * Resolves a host name (e.g. `'nodejs.org'`) into the first found A (IPv4) or + * AAAA (IPv6) record. All `option` properties are optional. If `options` is an + * integer, then it must be `4` or `6` – if `options` is not provided, then IPv4 + * and IPv6 addresses are both returned if found. + * + * With the `all` option set to `true`, the `Promise` is resolved with `addresses`being an array of objects with the properties `address` and `family`. + * + * On error, the `Promise` is rejected with an `Error` object, where `err.code`is the error code. + * Keep in mind that `err.code` will be set to `'ENOTFOUND'` not only when + * the host name does not exist but also when the lookup fails in other ways + * such as no available file descriptors. + * + * `dnsPromises.lookup()` does not necessarily have anything to do with the DNS + * protocol. The implementation uses an operating system facility that can + * associate names with addresses, and vice versa. This implementation can have + * subtle but important consequences on the behavior of any Node.js program. Please + * take some time to consult the `Implementation considerations section` before + * using `dnsPromises.lookup()`. + * + * Example usage: + * + * ```js + * const dns = require('dns'); + * const dnsPromises = dns.promises; + * const options = { + * family: 6, + * hints: dns.ADDRCONFIG | dns.V4MAPPED, + * }; + * + * dnsPromises.lookup('example.com', options).then((result) => { + * console.log('address: %j family: IPv%s', result.address, result.family); + * // address: "2606:2800:220:1:248:1893:25c8:1946" family: IPv6 + * }); + * + * // When options.all is true, the result will be an Array. + * options.all = true; + * dnsPromises.lookup('example.com', options).then((result) => { + * console.log('addresses: %j', result); + * // addresses: [{"address":"2606:2800:220:1:248:1893:25c8:1946","family":6}] + * }); + * ``` + * @since v10.6.0 + */ + function lookup(hostname: string, family: number): Promise<LookupAddress>; + function lookup( + hostname: string, + options: LookupOneOptions, + ): Promise<LookupAddress>; + function lookup( + hostname: string, + options: LookupAllOptions, + ): Promise<LookupAddress[]>; + function lookup( + hostname: string, + options: LookupOptions, + ): Promise<LookupAddress | LookupAddress[]>; + function lookup(hostname: string): Promise<LookupAddress>; + /** + * Resolves the given `address` and `port` into a host name and service using + * the operating system's underlying `getnameinfo` implementation. + * + * If `address` is not a valid IP address, a `TypeError` will be thrown. + * The `port` will be coerced to a number. If it is not a legal port, a `TypeError`will be thrown. + * + * On error, the `Promise` is rejected with an `Error` object, where `err.code`is the error code. + * + * ```js + * const dnsPromises = require('dns').promises; + * dnsPromises.lookupService('127.0.0.1', 22).then((result) => { + * console.log(result.hostname, result.service); + * // Prints: localhost ssh + * }); + * ``` + * @since v10.6.0 + */ + function lookupService( + address: string, + port: number, + ): Promise<{ + hostname: string; + service: string; + }>; + /** + * Uses the DNS protocol to resolve a host name (e.g. `'nodejs.org'`) into an array + * of the resource records. When successful, the `Promise` is resolved with an + * array of resource records. The type and structure of individual results vary + * based on `rrtype`: + * + * <omitted> + * + * On error, the `Promise` is rejected with an `Error` object, where `err.code`is one of the `DNS error codes`. + * @since v10.6.0 + * @param hostname Host name to resolve. + * @param [rrtype='A'] Resource record type. + */ + function resolve(hostname: string): Promise<string[]>; + // function resolve(hostname: string, rrtype: "A"): Promise<string[]>; + // function resolve(hostname: string, rrtype: "AAAA"): Promise<string[]>; + // function resolve(hostname: string, rrtype: "ANY"): Promise<AnyRecord[]>; + // function resolve(hostname: string, rrtype: "CAA"): Promise<CaaRecord[]>; + // function resolve(hostname: string, rrtype: "CNAME"): Promise<string[]>; + // function resolve(hostname: string, rrtype: "MX"): Promise<MxRecord[]>; + // function resolve(hostname: string, rrtype: "NAPTR"): Promise<NaptrRecord[]>; + // function resolve(hostname: string, rrtype: "NS"): Promise<string[]>; + // function resolve(hostname: string, rrtype: "PTR"): Promise<string[]>; + // function resolve(hostname: string, rrtype: "SOA"): Promise<SoaRecord>; + // function resolve(hostname: string, rrtype: "SRV"): Promise<SrvRecord[]>; + // function resolve(hostname: string, rrtype: "TXT"): Promise<string[][]>; + // function resolve( + // hostname: string, + // rrtype: string, + // ): Promise< + // | string[] + // | MxRecord[] + // | NaptrRecord[] + // | SoaRecord + // | SrvRecord[] + // | string[][] + // | AnyRecord[] + // >; + /** + * Uses the DNS protocol to resolve IPv4 addresses (`A` records) for the`hostname`. On success, the `Promise` is resolved with an array of IPv4 + * addresses (e.g. `['74.125.79.104', '74.125.79.105', '74.125.79.106']`). + * @since v10.6.0 + * @param hostname Host name to resolve. + */ + function resolve4(hostname: string): Promise<string[]>; + function resolve4( + hostname: string, + options: ResolveWithTtlOptions, + ): Promise<RecordWithTtl[]>; + function resolve4( + hostname: string, + options: ResolveOptions, + ): Promise<string[] | RecordWithTtl[]>; + /** + * Uses the DNS protocol to resolve IPv6 addresses (`AAAA` records) for the`hostname`. On success, the `Promise` is resolved with an array of IPv6 + * addresses. + * @since v10.6.0 + * @param hostname Host name to resolve. + */ + function resolve6(hostname: string): Promise<string[]>; + function resolve6( + hostname: string, + options: ResolveWithTtlOptions, + ): Promise<RecordWithTtl[]>; + function resolve6( + hostname: string, + options: ResolveOptions, + ): Promise<string[] | RecordWithTtl[]>; + /** + * Uses the DNS protocol to resolve all records (also known as `ANY` or `*` query). + * On success, the `Promise` is resolved with an array containing various types of + * records. Each object has a property `type` that indicates the type of the + * current record. And depending on the `type`, additional properties will be + * present on the object: + * + * <omitted> + * + * Here is an example of the result object: + * + * ```js + * [ { type: 'A', address: '127.0.0.1', ttl: 299 }, + * { type: 'CNAME', value: 'example.com' }, + * { type: 'MX', exchange: 'alt4.aspmx.l.example.com', priority: 50 }, + * { type: 'NS', value: 'ns1.example.com' }, + * { type: 'TXT', entries: [ 'v=spf1 include:_spf.example.com ~all' ] }, + * { type: 'SOA', + * nsname: 'ns1.example.com', + * hostmaster: 'admin.example.com', + * serial: 156696742, + * refresh: 900, + * retry: 900, + * expire: 1800, + * minttl: 60 } ] + * ``` + * @since v10.6.0 + */ + // function resolveAny(hostname: string): Promise<AnyRecord[]>; + /** + * Uses the DNS protocol to resolve `CAA` records for the `hostname`. On success, + * the `Promise` is resolved with an array of objects containing available + * certification authority authorization records available for the `hostname`(e.g. `[{critical: 0, iodef: 'mailto:pki@example.com'},{critical: 128, issue: 'pki.example.com'}]`). + * @since v15.0.0, v14.17.0 + */ + // function resolveCaa(hostname: string): Promise<CaaRecord[]>; + /** + * Uses the DNS protocol to resolve `CNAME` records for the `hostname`. On success, + * the `Promise` is resolved with an array of canonical name records available for + * the `hostname` (e.g. `['bar.example.com']`). + * @since v10.6.0 + */ + // function resolveCname(hostname: string): Promise<string[]>; + /** + * Uses the DNS protocol to resolve mail exchange records (`MX` records) for the`hostname`. On success, the `Promise` is resolved with an array of objects + * containing both a `priority` and `exchange` property (e.g.`[{priority: 10, exchange: 'mx.example.com'}, ...]`). + * @since v10.6.0 + */ + // function resolveMx(hostname: string): Promise<MxRecord[]>; + /** + * Uses the DNS protocol to resolve regular expression based records (`NAPTR`records) for the `hostname`. On success, the `Promise` is resolved with an array + * of objects with the following properties: + * + * * `flags` + * * `service` + * * `regexp` + * * `replacement` + * * `order` + * * `preference` + * + * ```js + * { + * flags: 's', + * service: 'SIP+D2U', + * regexp: '', + * replacement: '_sip._udp.example.com', + * order: 30, + * preference: 100 + * } + * ``` + * @since v10.6.0 + */ + // function resolveNaptr(hostname: string): Promise<NaptrRecord[]>; + /** + * Uses the DNS protocol to resolve name server records (`NS` records) for the`hostname`. On success, the `Promise` is resolved with an array of name server + * records available for `hostname` (e.g.`['ns1.example.com', 'ns2.example.com']`). + * @since v10.6.0 + */ + // function resolveNs(hostname: string): Promise<string[]>; + /** + * Uses the DNS protocol to resolve pointer records (`PTR` records) for the`hostname`. On success, the `Promise` is resolved with an array of strings + * containing the reply records. + * @since v10.6.0 + */ + // function resolvePtr(hostname: string): Promise<string[]>; + /** + * Uses the DNS protocol to resolve a start of authority record (`SOA` record) for + * the `hostname`. On success, the `Promise` is resolved with an object with the + * following properties: + * + * * `nsname` + * * `hostmaster` + * * `serial` + * * `refresh` + * * `retry` + * * `expire` + * * `minttl` + * + * ```js + * { + * nsname: 'ns.example.com', + * hostmaster: 'root.example.com', + * serial: 2013101809, + * refresh: 10000, + * retry: 2400, + * expire: 604800, + * minttl: 3600 + * } + * ``` + * @since v10.6.0 + */ + // function resolveSoa(hostname: string): Promise<SoaRecord>; + /** + * Uses the DNS protocol to resolve service records (`SRV` records) for the`hostname`. On success, the `Promise` is resolved with an array of objects with + * the following properties: + * + * * `priority` + * * `weight` + * * `port` + * * `name` + * + * ```js + * { + * priority: 10, + * weight: 5, + * port: 21223, + * name: 'service.example.com' + * } + * ``` + * @since v10.6.0 + */ + // function resolveSrv(hostname: string): Promise<SrvRecord[]>; + /** + * Uses the DNS protocol to resolve text queries (`TXT` records) for the`hostname`. On success, the `Promise` is resolved with a two-dimensional array + * of the text records available for `hostname` (e.g.`[ ['v=spf1 ip4:0.0.0.0 ', '~all' ] ]`). Each sub-array contains TXT chunks of + * one record. Depending on the use case, these could be either joined together or + * treated separately. + * @since v10.6.0 + */ + // function resolveTxt(hostname: string): Promise<string[][]>; + /** + * Performs a reverse DNS query that resolves an IPv4 or IPv6 address to an + * array of host names. + * + * On error, the `Promise` is rejected with an `Error` object, where `err.code`is one of the `DNS error codes`. + * @since v10.6.0 + */ + // function reverse(ip: string): Promise<string[]>; + /** + * Sets the IP address and port of servers to be used when performing DNS + * resolution. The `servers` argument is an array of [RFC 5952](https://tools.ietf.org/html/rfc5952#section-6) formatted + * addresses. If the port is the IANA default DNS port (53) it can be omitted. + * + * ```js + * dnsPromises.setServers([ + * '4.4.4.4', + * '[2001:4860:4860::8888]', + * '4.4.4.4:1053', + * '[2001:4860:4860::8888]:1053', + * ]); + * ``` + * + * An error will be thrown if an invalid address is provided. + * + * The `dnsPromises.setServers()` method must not be called while a DNS query is in + * progress. + * + * This method works much like [resolve.conf](https://man7.org/linux/man-pages/man5/resolv.conf.5.html). + * That is, if attempting to resolve with the first server provided results in a`NOTFOUND` error, the `resolve()` method will _not_ attempt to resolve with + * subsequent servers provided. Fallback DNS servers will only be used if the + * earlier ones time out or result in some other error. + * @since v10.6.0 + * @param servers array of `RFC 5952` formatted addresses + */ + // function setServers(servers: ReadonlyArray<string>): void; + /** + * Set the default value of `verbatim` in `dns.lookup()` and `dnsPromises.lookup()`. The value could be: + * + * * `ipv4first`: sets default `verbatim` `false`. + * * `verbatim`: sets default `verbatim` `true`. + * + * The default is `ipv4first` and `dnsPromises.setDefaultResultOrder()` have + * higher priority than `--dns-result-order`. When using `worker threads`,`dnsPromises.setDefaultResultOrder()` from the main thread won't affect the + * default dns orders in workers. + * @since v16.4.0, v14.18.0 + * @param order must be `'ipv4first'` or `'verbatim'`. + */ + // function setDefaultResultOrder(order: "ipv4first" | "verbatim"): void; + class Resolver { + constructor(options?: ResolverOptions); + cancel(): void; + // getServers: typeof getServers; + resolve: typeof resolve; + resolve4: typeof resolve4; + resolve6: typeof resolve6; + // resolveAny: typeof resolveAny; + // resolveCname: typeof resolveCname; + // resolveMx: typeof resolveMx; + // resolveNaptr: typeof resolveNaptr; + // resolveNs: typeof resolveNs; + // resolvePtr: typeof resolvePtr; + // resolveSoa: typeof resolveSoa; + // resolveSrv: typeof resolveSrv; + // resolveTxt: typeof resolveTxt; + // reverse: typeof reverse; + // setLocalAddress(ipv4?: string, ipv6?: string): void; + // setServers: typeof setServers; + } +} +declare module "node:dns/promises" { + export * from "dns/promises"; +} diff --git a/packages/bun-types/fs.d.ts b/packages/bun-types/fs.d.ts index c4fc1f99c..7e34d5873 100644 --- a/packages/bun-types/fs.d.ts +++ b/packages/bun-types/fs.d.ts @@ -19,7 +19,7 @@ */ declare module "fs" { import * as stream from "stream"; - import type { SystemError } from "bun"; + import type { SystemError, ArrayBufferView } from "bun"; interface ObjectEncodingOptions { encoding?: BufferEncoding | null | undefined; diff --git a/packages/bun-types/fs/promises.d.ts b/packages/bun-types/fs/promises.d.ts index 3749ca058..c4356da5c 100644 --- a/packages/bun-types/fs/promises.d.ts +++ b/packages/bun-types/fs/promises.d.ts @@ -8,6 +8,7 @@ * concurrent modifications on the same file or data corruption may occur. */ declare module "fs/promises" { + import { ArrayBufferView } from "bun"; import { Stats, BigIntStats, diff --git a/packages/bun-types/globals.d.ts b/packages/bun-types/globals.d.ts index a27ee2b92..8cf57b05a 100644 --- a/packages/bun-types/globals.d.ts +++ b/packages/bun-types/globals.d.ts @@ -351,7 +351,7 @@ interface Process { revision: string; chdir(directory: string): void; cwd(): string; - exit(code?: number): void; + exit(code?: number): never; getgid(): number; setgid(id: number | string): void; getuid(): number; @@ -1854,7 +1854,7 @@ declare var AbortSignal: { // type AlgorithmIdentifier = Algorithm | string; // type BodyInit = ReadableStream | XMLHttpRequestBodyInit; -type BufferSource = ArrayBufferView | ArrayBuffer | SharedArrayBuffer; +type BufferSource = TypedArray | DataView | ArrayBufferLike; // type COSEAlgorithmIdentifier = number; // type CSSNumberish = number; // type CanvasImageSource = diff --git a/packages/bun-types/net.d.ts b/packages/bun-types/net.d.ts new file mode 100644 index 000000000..1e373f4ba --- /dev/null +++ b/packages/bun-types/net.d.ts @@ -0,0 +1,1007 @@ +/** + * > Stability: 2 - Stable + * + * The `net` module provides an asynchronous network API for creating stream-based + * TCP or `IPC` servers ({@link createServer}) and clients + * ({@link createConnection}). + * + * It can be accessed using: + * + * ```js + * const net = require('net'); + * ``` + * @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/net.js) + */ +declare module "net" { + import * as stream from "node:stream"; + import { + Abortable, + // EventEmitter + } from "node:events"; + // import * as dns from "node:dns"; + // type LookupFunction = ( + // hostname: string, + // options: dns.LookupOneOptions, + // callback: ( + // err: NodeJS.ErrnoException | null, + // address: string, + // family: number, + // ) => void, + // ) => void; + interface AddressInfo { + address: string; + family: string; + port: number; + } + interface SocketConstructorOpts { + // fd?: number | undefined; + allowHalfOpen?: boolean | undefined; + readable?: boolean | undefined; + writable?: boolean | undefined; + signal?: AbortSignal; + } + interface OnReadOpts { + buffer: Uint8Array | (() => Uint8Array); + /** + * This function is called for every chunk of incoming data. + * Two arguments are passed to it: the number of bytes written to buffer and a reference to buffer. + * Return false from this function to implicitly pause() the socket. + */ + callback(bytesWritten: number, buf: Uint8Array): boolean; + } + interface ConnectOpts { + /** + * If specified, incoming data is stored in a single buffer and passed to the supplied callback when data arrives on the socket. + * Note: this will cause the streaming functionality to not provide any data, however events like 'error', 'end', and 'close' will + * still be emitted as normal and methods like pause() and resume() will also behave as expected. + */ + onread?: OnReadOpts | undefined; + } + interface TcpSocketConnectOpts extends ConnectOpts { + port: number; + host?: string | undefined; + // localAddress?: string | undefined; + // localPort?: number | undefined; + // hints?: number | undefined; + // family?: number | undefined; + // lookup?: LookupFunction | undefined; + // noDelay?: boolean | undefined; + // keepAlive?: boolean | undefined; + // keepAliveInitialDelay?: number | undefined; + } + // interface IpcSocketConnectOpts extends ConnectOpts { + // path: string; + // } + type SocketConnectOpts = TcpSocketConnectOpts; // | IpcSocketConnectOpts; + type SocketReadyState = + | "opening" + | "open" + | "readOnly" + | "writeOnly" + | "closed"; + /** + * This class is an abstraction of a TCP socket or a streaming `IPC` endpoint + * (uses named pipes on Windows, and Unix domain sockets otherwise). It is also + * an `EventEmitter`. + * + * A `net.Socket` can be created by the user and used directly to interact with + * a server. For example, it is returned by {@link createConnection}, + * so the user can use it to talk to the server. + * + * It can also be created by Node.js and passed to the user when a connection + * is received. For example, it is passed to the listeners of a `'connection'` event emitted on a {@link Server}, so the user can use + * it to interact with the client. + * @since v0.3.4 + */ + class Socket extends stream.Duplex { + constructor(options?: SocketConstructorOpts); + /** + * Sends data on the socket. The second parameter specifies the encoding in the + * case of a string. It defaults to UTF8 encoding. + * + * Returns `true` if the entire data was flushed successfully to the kernel + * buffer. Returns `false` if all or part of the data was queued in user memory.`'drain'` will be emitted when the buffer is again free. + * + * The optional `callback` parameter will be executed when the data is finally + * written out, which may not be immediately. + * + * See `Writable` stream `write()` method for more + * information. + * @since v0.1.90 + * @param [encoding='utf8'] Only used when data is `string`. + */ + write(buffer: Uint8Array | string, cb?: (err?: Error) => void): boolean; + write( + str: Uint8Array | string, + encoding?: BufferEncoding, + cb?: (err?: Error) => void, + ): boolean; + /** + * Initiate a connection on a given socket. + * + * Possible signatures: + * + * * `socket.connect(options[, connectListener])` + * * `socket.connect(path[, connectListener])` for `IPC` connections. + * * `socket.connect(port[, host][, connectListener])` for TCP connections. + * * Returns: `net.Socket` The socket itself. + * + * This function is asynchronous. When the connection is established, the `'connect'` event will be emitted. If there is a problem connecting, + * instead of a `'connect'` event, an `'error'` event will be emitted with + * the error passed to the `'error'` listener. + * The last parameter `connectListener`, if supplied, will be added as a listener + * for the `'connect'` event **once**. + * + * This function should only be used for reconnecting a socket after`'close'` has been emitted or otherwise it may lead to undefined + * behavior. + */ + connect(options: SocketConnectOpts, connectionListener?: () => void): this; + connect(port: number, host: string, connectionListener?: () => void): this; + connect(port: number, connectionListener?: () => void): this; + connect(path: string, connectionListener?: () => void): this; + /** + * Set the encoding for the socket as a `Readable Stream`. See `readable.setEncoding()` for more information. + * @since v0.1.90 + * @return The socket itself. + */ + setEncoding(encoding?: BufferEncoding): this; + /** + * Pauses the reading of data. That is, `'data'` events will not be emitted. + * Useful to throttle back an upload. + * @return The socket itself. + */ + pause(): this; + /** + * Close the TCP connection by sending an RST packet and destroy the stream. + * If this TCP socket is in connecting status, it will send an RST packet + * and destroy this TCP socket once it is connected. Otherwise, it will call + * `socket.destroy` with an `ERR_SOCKET_CLOSED` Error. If this is not a TCP socket + * (for example, a pipe), calling this method will immediately throw + * an `ERR_INVALID_HANDLE_TYPE` Error. + * @since v18.3.0 + * @return The socket itself. + */ + resetAndDestroy(): this; + /** + * Resumes reading after a call to `socket.pause()`. + * @return The socket itself. + */ + resume(): this; + /** + * Sets the socket to timeout after `timeout` milliseconds of inactivity on + * the socket. By default `net.Socket` do not have a timeout. + * + * When an idle timeout is triggered the socket will receive a `'timeout'` event but the connection will not be severed. The user must manually call `socket.end()` or `socket.destroy()` to + * end the connection. + * + * ```js + * socket.setTimeout(3000); + * socket.on('timeout', () => { + * console.log('socket timeout'); + * socket.end(); + * }); + * ``` + * + * If `timeout` is 0, then the existing idle timeout is disabled. + * + * The optional `callback` parameter will be added as a one-time listener for the `'timeout'` event. + * @since v0.1.90 + * @return The socket itself. + */ + setTimeout(timeout: number, callback?: () => void): this; + /** + * Enable/disable the use of Nagle's algorithm. + * + * When a TCP connection is created, it will have Nagle's algorithm enabled. + * + * Nagle's algorithm delays data before it is sent via the network. It attempts + * to optimize throughput at the expense of latency. + * + * Passing `true` for `noDelay` or not passing an argument will disable Nagle's + * algorithm for the socket. Passing `false` for `noDelay` will enable Nagle's + * algorithm. + * @since v0.1.90 + * @param [noDelay=true] + * @return The socket itself. + */ + setNoDelay(noDelay?: boolean): this; + /** + * Enable/disable keep-alive functionality, and optionally set the initial + * delay before the first keepalive probe is sent on an idle socket. + * + * Set `initialDelay` (in milliseconds) to set the delay between the last + * data packet received and the first keepalive probe. Setting `0` for`initialDelay` will leave the value unchanged from the default + * (or previous) setting. + * + * Enabling the keep-alive functionality will set the following socket options: + * + * * `SO_KEEPALIVE=1` + * * `TCP_KEEPIDLE=initialDelay` + * * `TCP_KEEPCNT=10` + * * `TCP_KEEPINTVL=1` + * @since v0.1.92 + * @param [enable=false] + * @param [initialDelay=0] + * @return The socket itself. + */ + setKeepAlive(enable?: boolean, initialDelay?: number): this; + /** + * Returns the bound `address`, the address `family` name and `port` of the + * socket as reported by the operating system:`{ port: 12346, family: 'IPv4', address: '127.0.0.1' }` + * @since v0.1.90 + */ + address(): AddressInfo | {}; + /** + * Calling `unref()` on a socket will allow the program to exit if this is the only + * active socket in the event system. If the socket is already `unref`ed calling`unref()` again will have no effect. + * @since v0.9.1 + * @return The socket itself. + */ + unref(): this; + /** + * Opposite of `unref()`, calling `ref()` on a previously `unref`ed socket will _not_ let the program exit if it's the only socket left (the default behavior). + * If the socket is `ref`ed calling `ref` again will have no effect. + * @since v0.9.1 + * @return The socket itself. + */ + ref(): this; + /** + * This property shows the number of characters buffered for writing. The buffer + * may contain strings whose length after encoding is not yet known. So this number + * is only an approximation of the number of bytes in the buffer. + * + * `net.Socket` has the property that `socket.write()` always works. This is to + * help users get up and running quickly. The computer cannot always keep up + * with the amount of data that is written to a socket. The network connection + * simply might be too slow. Node.js will internally queue up the data written to a + * socket and send it out over the wire when it is possible. + * + * The consequence of this internal buffering is that memory may grow. + * Users who experience large or growing `bufferSize` should attempt to + * "throttle" the data flows in their program with `socket.pause()` and `socket.resume()`. + * @since v0.3.8 + * @deprecated Since v14.6.0 - Use `writableLength` instead. + */ + readonly bufferSize: number; + /** + * The amount of received bytes. + * @since v0.5.3 + */ + readonly bytesRead: number; + /** + * The amount of bytes sent. + * @since v0.5.3 + */ + readonly bytesWritten: number; + /** + * If `true`,`socket.connect(options[, connectListener])` was + * called and has not yet finished. It will stay `true` until the socket becomes + * connected, then it is set to `false` and the `'connect'` event is emitted. Note + * that the `socket.connect(options[, connectListener])` callback is a listener for the `'connect'` event. + * @since v6.1.0 + */ + readonly connecting: boolean; + /** + * See `writable.destroyed` for further details. + */ + // readonly destroyed: boolean; + /** + * The string representation of the local IP address the remote client is + * connecting on. For example, in a server listening on `'0.0.0.0'`, if a client + * connects on `'192.168.1.1'`, the value of `socket.localAddress` would be`'192.168.1.1'`. + * @since v0.9.6 + */ + readonly localAddress?: string; + /** + * The numeric representation of the local port. For example, `80` or `21`. + * @since v0.9.6 + */ + readonly localPort?: number; + /** + * The string representation of the local IP family. `'IPv4'` or `'IPv6'`. + * @since v18.8.0 + */ + readonly localFamily?: string; + /** + * This property represents the state of the connection as a string. + * @see {https://nodejs.org/api/net.html#socketreadystate} + * @since v0.5.0 + */ + readonly readyState: SocketReadyState; + /** + * The string representation of the remote IP address. For example,`'74.125.127.100'` or `'2001:4860:a005::68'`. Value may be `undefined` if + * the socket is destroyed (for example, if the client disconnected). + * @since v0.5.10 + */ + readonly remoteAddress?: string | undefined; + /** + * The string representation of the remote IP family. `'IPv4'` or `'IPv6'`. + * @since v0.11.14 + */ + readonly remoteFamily?: string | undefined; + /** + * The numeric representation of the remote port. For example, `80` or `21`. + * @since v0.5.10 + */ + readonly remotePort?: number | undefined; + /** + * The socket timeout in milliseconds as set by socket.setTimeout(). It is undefined if a timeout has not been set. + * @since v10.7.0 + */ + readonly timeout?: number | undefined; + /** + * Half-closes the socket. i.e., it sends a FIN packet. It is possible the + * server will still send some data. + * + * See `writable.end()` for further details. + * @since v0.1.90 + * @param [encoding='utf8'] Only used when data is `string`. + * @param callback Optional callback for when the socket is finished. + * @return The socket itself. + */ + end(callback?: () => void): this; + end(buffer: Uint8Array | string, callback?: () => void): this; + end( + str: Uint8Array | string, + encoding?: BufferEncoding, + callback?: () => void, + ): this; + /** + * events.EventEmitter + * 1. close + * 2. connect + * 3. data + * 4. drain + * 5. end + * 6. error + * 7. lookup + * 8. ready + * 9. timeout + */ + addListener(event: string, listener: (...args: any[]) => void): this; + addListener(event: "close", listener: (hadError: boolean) => void): this; + addListener(event: "connect", listener: () => void): this; + addListener(event: "data", listener: (data: Buffer) => void): this; + addListener(event: "drain", listener: () => void): this; + addListener(event: "end", listener: () => void): this; + addListener(event: "error", listener: (err: Error) => void): this; + addListener( + event: "lookup", + listener: ( + err: Error, + address: string, + family: string | number, + host: string, + ) => void, + ): this; + addListener(event: "ready", listener: () => void): this; + addListener(event: "timeout", listener: () => void): this; + emit(event: string | symbol, ...args: any[]): boolean; + emit(event: "close", hadError: boolean): boolean; + emit(event: "connect"): boolean; + emit(event: "data", data: Buffer): boolean; + emit(event: "drain"): boolean; + emit(event: "end"): boolean; + emit(event: "error", err: Error): boolean; + emit( + event: "lookup", + err: Error, + address: string, + family: string | number, + host: string, + ): boolean; + emit(event: "ready"): boolean; + emit(event: "timeout"): boolean; + on(event: string, listener: (...args: any[]) => void): this; + on(event: "close", listener: (hadError: boolean) => void): this; + on(event: "connect", listener: () => void): this; + on(event: "data", listener: (data: Buffer) => void): this; + on(event: "drain", listener: () => void): this; + on(event: "end", listener: () => void): this; + on(event: "error", listener: (err: Error) => void): this; + on( + event: "lookup", + listener: ( + err: Error, + address: string, + family: string | number, + host: string, + ) => void, + ): this; + on(event: "ready", listener: () => void): this; + on(event: "timeout", listener: () => void): this; + once(event: string, listener: (...args: any[]) => void): this; + once(event: "close", listener: (hadError: boolean) => void): this; + once(event: "connect", listener: () => void): this; + once(event: "data", listener: (data: Buffer) => void): this; + once(event: "drain", listener: () => void): this; + once(event: "end", listener: () => void): this; + once(event: "error", listener: (err: Error) => void): this; + once( + event: "lookup", + listener: ( + err: Error, + address: string, + family: string | number, + host: string, + ) => void, + ): this; + once(event: "ready", listener: () => void): this; + once(event: "timeout", listener: () => void): this; + prependListener(event: string, listener: (...args: any[]) => void): this; + prependListener( + event: "close", + listener: (hadError: boolean) => void, + ): this; + prependListener(event: "connect", listener: () => void): this; + prependListener(event: "data", listener: (data: Buffer) => void): this; + prependListener(event: "drain", listener: () => void): this; + prependListener(event: "end", listener: () => void): this; + prependListener(event: "error", listener: (err: Error) => void): this; + prependListener( + event: "lookup", + listener: ( + err: Error, + address: string, + family: string | number, + host: string, + ) => void, + ): this; + prependListener(event: "ready", listener: () => void): this; + prependListener(event: "timeout", listener: () => void): this; + prependOnceListener( + event: string, + listener: (...args: any[]) => void, + ): this; + prependOnceListener( + event: "close", + listener: (hadError: boolean) => void, + ): this; + prependOnceListener(event: "connect", listener: () => void): this; + prependOnceListener(event: "data", listener: (data: Buffer) => void): this; + prependOnceListener(event: "drain", listener: () => void): this; + prependOnceListener(event: "end", listener: () => void): this; + prependOnceListener(event: "error", listener: (err: Error) => void): this; + prependOnceListener( + event: "lookup", + listener: ( + err: Error, + address: string, + family: string | number, + host: string, + ) => void, + ): this; + prependOnceListener(event: "ready", listener: () => void): this; + prependOnceListener(event: "timeout", listener: () => void): this; + } + interface ListenOptions extends Abortable { + port?: number | undefined; + host?: string | undefined; + backlog?: number | undefined; + path?: string | undefined; + exclusive?: boolean | undefined; + readableAll?: boolean | undefined; + writableAll?: boolean | undefined; + /** + * @default false + */ + ipv6Only?: boolean | undefined; + } + interface ServerOpts { + /** + * Indicates whether half-opened TCP connections are allowed. + * @default false + */ + allowHalfOpen?: boolean | undefined; + /** + * Indicates whether the socket should be paused on incoming connections. + * @default false + */ + pauseOnConnect?: boolean | undefined; + /** + * If set to `true`, it disables the use of Nagle's algorithm immediately after a new incoming connection is received. + * @default false + * @since v16.5.0 + */ + noDelay?: boolean | undefined; + /** + * If set to `true`, it enables keep-alive functionality on the socket immediately after a new incoming connection is received, + * similarly on what is done in `socket.setKeepAlive([enable][, initialDelay])`. + * @default false + * @since v16.5.0 + */ + keepAlive?: boolean | undefined; + /** + * If set to a positive number, it sets the initial delay before the first keepalive probe is sent on an idle socket. + * @default 0 + * @since v16.5.0 + */ + keepAliveInitialDelay?: number | undefined; + } + interface DropArgument { + localAddress?: string; + localPort?: number; + localFamily?: string; + remoteAddress?: string; + remotePort?: number; + remoteFamily?: string; + } + /** + * This class is used to create a TCP or `IPC` server. + * @since v0.1.90 + */ + // class Server extends EventEmitter { + // constructor(connectionListener?: (socket: Socket) => void); + // constructor( + // options?: ServerOpts, + // connectionListener?: (socket: Socket) => void, + // ); + // /** + // * Start a server listening for connections. A `net.Server` can be a TCP or + // * an `IPC` server depending on what it listens to. + // * + // * Possible signatures: + // * + // * * `server.listen(handle[, backlog][, callback])` + // * * `server.listen(options[, callback])` + // * * `server.listen(path[, backlog][, callback])` for `IPC` servers + // * * `server.listen([port[, host[, backlog]]][, callback])` for TCP servers + // * + // * This function is asynchronous. When the server starts listening, the `'listening'` event will be emitted. The last parameter `callback`will be added as a listener for the `'listening'` + // * event. + // * + // * All `listen()` methods can take a `backlog` parameter to specify the maximum + // * length of the queue of pending connections. The actual length will be determined + // * by the OS through sysctl settings such as `tcp_max_syn_backlog` and `somaxconn`on Linux. The default value of this parameter is 511 (not 512). + // * + // * All {@link Socket} are set to `SO_REUSEADDR` (see [`socket(7)`](https://man7.org/linux/man-pages/man7/socket.7.html) for + // * details). + // * + // * The `server.listen()` method can be called again if and only if there was an + // * error during the first `server.listen()` call or `server.close()` has been + // * called. Otherwise, an `ERR_SERVER_ALREADY_LISTEN` error will be thrown. + // * + // * One of the most common errors raised when listening is `EADDRINUSE`. + // * This happens when another server is already listening on the requested`port`/`path`/`handle`. One way to handle this would be to retry + // * after a certain amount of time: + // * + // * ```js + // * server.on('error', (e) => { + // * if (e.code === 'EADDRINUSE') { + // * console.log('Address in use, retrying...'); + // * setTimeout(() => { + // * server.close(); + // * server.listen(PORT, HOST); + // * }, 1000); + // * } + // * }); + // * ``` + // */ + // listen( + // port?: number, + // hostname?: string, + // backlog?: number, + // listeningListener?: () => void, + // ): this; + // listen( + // port?: number, + // hostname?: string, + // listeningListener?: () => void, + // ): this; + // listen( + // port?: number, + // backlog?: number, + // listeningListener?: () => void, + // ): this; + // listen(port?: number, listeningListener?: () => void): this; + // listen( + // path: string, + // backlog?: number, + // listeningListener?: () => void, + // ): this; + // listen(path: string, listeningListener?: () => void): this; + // listen(options: ListenOptions, listeningListener?: () => void): this; + // listen(handle: any, backlog?: number, listeningListener?: () => void): this; + // listen(handle: any, listeningListener?: () => void): this; + // /** + // * Stops the server from accepting new connections and keeps existing + // * connections. This function is asynchronous, the server is finally closed + // * when all connections are ended and the server emits a `'close'` event. + // * The optional `callback` will be called once the `'close'` event occurs. Unlike + // * that event, it will be called with an `Error` as its only argument if the server + // * was not open when it was closed. + // * @since v0.1.90 + // * @param callback Called when the server is closed. + // */ + // close(callback?: (err?: Error) => void): this; + // /** + // * Returns the bound `address`, the address `family` name, and `port` of the server + // * as reported by the operating system if listening on an IP socket + // * (useful to find which port was assigned when getting an OS-assigned address):`{ port: 12346, family: 'IPv4', address: '127.0.0.1' }`. + // * + // * For a server listening on a pipe or Unix domain socket, the name is returned + // * as a string. + // * + // * ```js + // * const server = net.createServer((socket) => { + // * socket.end('goodbye\n'); + // * }).on('error', (err) => { + // * // Handle errors here. + // * throw err; + // * }); + // * + // * // Grab an arbitrary unused port. + // * server.listen(() => { + // * console.log('opened server on', server.address()); + // * }); + // * ``` + // * + // * `server.address()` returns `null` before the `'listening'` event has been + // * emitted or after calling `server.close()`. + // * @since v0.1.90 + // */ + // address(): AddressInfo | string | null; + // /** + // * Asynchronously get the number of concurrent connections on the server. Works + // * when sockets were sent to forks. + // * + // * Callback should take two arguments `err` and `count`. + // * @since v0.9.7 + // */ + // getConnections(cb: (error: Error | null, count: number) => void): void; + // /** + // * Opposite of `unref()`, calling `ref()` on a previously `unref`ed server will _not_ let the program exit if it's the only server left (the default behavior). + // * If the server is `ref`ed calling `ref()` again will have no effect. + // * @since v0.9.1 + // */ + // ref(): this; + // /** + // * Calling `unref()` on a server will allow the program to exit if this is the only + // * active server in the event system. If the server is already `unref`ed calling`unref()` again will have no effect. + // * @since v0.9.1 + // */ + // unref(): this; + // /** + // * Set this property to reject connections when the server's connection count gets + // * high. + // * + // * It is not recommended to use this option once a socket has been sent to a child + // * with `child_process.fork()`. + // * @since v0.2.0 + // */ + // maxConnections: number; + // connections: number; + // /** + // * Indicates whether or not the server is listening for connections. + // * @since v5.7.0 + // */ + // listening: boolean; + // /** + // * events.EventEmitter + // * 1. close + // * 2. connection + // * 3. error + // * 4. listening + // * 5. drop + // */ + // addListener(event: string, listener: (...args: any[]) => void): this; + // addListener(event: "close", listener: () => void): this; + // addListener(event: "connection", listener: (socket: Socket) => void): this; + // addListener(event: "error", listener: (err: Error) => void): this; + // addListener(event: "listening", listener: () => void): this; + // addListener(event: "drop", listener: (data?: DropArgument) => void): this; + // emit(event: string | symbol, ...args: any[]): boolean; + // emit(event: "close"): boolean; + // emit(event: "connection", socket: Socket): boolean; + // emit(event: "error", err: Error): boolean; + // emit(event: "listening"): boolean; + // emit(event: "drop", data?: DropArgument): boolean; + // on(event: string, listener: (...args: any[]) => void): this; + // on(event: "close", listener: () => void): this; + // on(event: "connection", listener: (socket: Socket) => void): this; + // on(event: "error", listener: (err: Error) => void): this; + // on(event: "listening", listener: () => void): this; + // on(event: "drop", listener: (data?: DropArgument) => void): this; + // once(event: string, listener: (...args: any[]) => void): this; + // once(event: "close", listener: () => void): this; + // once(event: "connection", listener: (socket: Socket) => void): this; + // once(event: "error", listener: (err: Error) => void): this; + // once(event: "listening", listener: () => void): this; + // once(event: "drop", listener: (data?: DropArgument) => void): this; + // prependListener(event: string, listener: (...args: any[]) => void): this; + // prependListener(event: "close", listener: () => void): this; + // prependListener( + // event: "connection", + // listener: (socket: Socket) => void, + // ): this; + // prependListener(event: "error", listener: (err: Error) => void): this; + // prependListener(event: "listening", listener: () => void): this; + // prependListener( + // event: "drop", + // listener: (data?: DropArgument) => void, + // ): this; + // prependOnceListener( + // event: string, + // listener: (...args: any[]) => void, + // ): this; + // prependOnceListener(event: "close", listener: () => void): this; + // prependOnceListener( + // event: "connection", + // listener: (socket: Socket) => void, + // ): this; + // prependOnceListener(event: "error", listener: (err: Error) => void): this; + // prependOnceListener(event: "listening", listener: () => void): this; + // prependOnceListener( + // event: "drop", + // listener: (data?: DropArgument) => void, + // ): this; + // } + type IPVersion = "ipv4" | "ipv6"; + /** + * The `BlockList` object can be used with some network APIs to specify rules for + * disabling inbound or outbound access to specific IP addresses, IP ranges, or + * IP subnets. + * @since v15.0.0, v14.18.0 + */ + // class BlockList { + // /** + // * Adds a rule to block the given IP address. + // * @since v15.0.0, v14.18.0 + // * @param address An IPv4 or IPv6 address. + // * @param [type='ipv4'] Either `'ipv4'` or `'ipv6'`. + // */ + // addAddress(address: string, type?: IPVersion): void; + // addAddress(address: SocketAddress): void; + // /** + // * Adds a rule to block a range of IP addresses from `start` (inclusive) to`end` (inclusive). + // * @since v15.0.0, v14.18.0 + // * @param start The starting IPv4 or IPv6 address in the range. + // * @param end The ending IPv4 or IPv6 address in the range. + // * @param [type='ipv4'] Either `'ipv4'` or `'ipv6'`. + // */ + // addRange(start: string, end: string, type?: IPVersion): void; + // addRange(start: SocketAddress, end: SocketAddress): void; + // /** + // * Adds a rule to block a range of IP addresses specified as a subnet mask. + // * @since v15.0.0, v14.18.0 + // * @param net The network IPv4 or IPv6 address. + // * @param prefix The number of CIDR prefix bits. For IPv4, this must be a value between `0` and `32`. For IPv6, this must be between `0` and `128`. + // * @param [type='ipv4'] Either `'ipv4'` or `'ipv6'`. + // */ + // addSubnet(net: SocketAddress, prefix: number): void; + // addSubnet(net: string, prefix: number, type?: IPVersion): void; + // /** + // * Returns `true` if the given IP address matches any of the rules added to the`BlockList`. + // * + // * ```js + // * const blockList = new net.BlockList(); + // * blockList.addAddress('123.123.123.123'); + // * blockList.addRange('10.0.0.1', '10.0.0.10'); + // * blockList.addSubnet('8592:757c:efae:4e45::', 64, 'ipv6'); + // * + // * console.log(blockList.check('123.123.123.123')); // Prints: true + // * console.log(blockList.check('10.0.0.3')); // Prints: true + // * console.log(blockList.check('222.111.111.222')); // Prints: false + // * + // * // IPv6 notation for IPv4 addresses works: + // * console.log(blockList.check('::ffff:7b7b:7b7b', 'ipv6')); // Prints: true + // * console.log(blockList.check('::ffff:123.123.123.123', 'ipv6')); // Prints: true + // * ``` + // * @since v15.0.0, v14.18.0 + // * @param address The IP address to check + // * @param [type='ipv4'] Either `'ipv4'` or `'ipv6'`. + // */ + // check(address: SocketAddress): boolean; + // check(address: string, type?: IPVersion): boolean; + // } + interface TcpNetConnectOpts + extends TcpSocketConnectOpts, + SocketConstructorOpts { + timeout?: number | undefined; + } + // interface IpcNetConnectOpts + // extends IpcSocketConnectOpts, + // SocketConstructorOpts { + // timeout?: number | undefined; + // } + type NetConnectOpts = TcpNetConnectOpts; //| IpcNetConnectOpts; + /** + * Creates a new TCP or `IPC` server. + * + * If `allowHalfOpen` is set to `true`, when the other end of the socket + * signals the end of transmission, the server will only send back the end of + * transmission when `socket.end()` is explicitly called. For example, in the + * context of TCP, when a FIN packed is received, a FIN packed is sent + * back only when `socket.end()` is explicitly called. Until then the + * connection is half-closed (non-readable but still writable). See `'end'` event and [RFC 1122](https://tools.ietf.org/html/rfc1122) (section 4.2.2.13) for more information. + * + * If `pauseOnConnect` is set to `true`, then the socket associated with each + * incoming connection will be paused, and no data will be read from its handle. + * This allows connections to be passed between processes without any data being + * read by the original process. To begin reading data from a paused socket, call `socket.resume()`. + * + * The server can be a TCP server or an `IPC` server, depending on what it `listen()` to. + * + * Here is an example of a TCP echo server which listens for connections + * on port 8124: + * + * ```js + * const net = require('net'); + * const server = net.createServer((c) => { + * // 'connection' listener. + * console.log('client connected'); + * c.on('end', () => { + * console.log('client disconnected'); + * }); + * c.write('hello\r\n'); + * c.pipe(c); + * }); + * server.on('error', (err) => { + * throw err; + * }); + * server.listen(8124, () => { + * console.log('server bound'); + * }); + * ``` + * + * Test this by using `telnet`: + * + * ```console + * $ telnet localhost 8124 + * ``` + * + * To listen on the socket `/tmp/echo.sock`: + * + * ```js + * server.listen('/tmp/echo.sock', () => { + * console.log('server bound'); + * }); + * ``` + * + * Use `nc` to connect to a Unix domain socket server: + * + * ```console + * $ nc -U /tmp/echo.sock + * ``` + * @since v0.5.0 + * @param connectionListener Automatically set as a listener for the {@link 'connection'} event. + */ + // function createServer(connectionListener?: (socket: Socket) => void): Server; + // function createServer( + // options?: ServerOpts, + // connectionListener?: (socket: Socket) => void, + // ): Server; + /** + * Aliases to {@link createConnection}. + * + * Possible signatures: + * + * * {@link connect} + * * {@link connect} for `IPC` connections. + * * {@link connect} for TCP connections. + */ + function connect( + options: NetConnectOpts, + connectionListener?: () => void, + ): Socket; + function connect( + port: number, + host?: string, + connectionListener?: () => void, + ): Socket; + function connect(path: string, connectionListener?: () => void): Socket; + /** + * A factory function, which creates a new {@link Socket}, + * immediately initiates connection with `socket.connect()`, + * then returns the `net.Socket` that starts the connection. + * + * When the connection is established, a `'connect'` event will be emitted + * on the returned socket. The last parameter `connectListener`, if supplied, + * will be added as a listener for the `'connect'` event **once**. + * + * Possible signatures: + * + * * {@link createConnection} + * * {@link createConnection} for `IPC` connections. + * * {@link createConnection} for TCP connections. + * + * The {@link connect} function is an alias to this function. + */ + function createConnection( + options: NetConnectOpts, + connectionListener?: () => void, + ): Socket; + function createConnection( + port: number, + host?: string, + connectionListener?: () => void, + ): Socket; + function createConnection( + path: string, + connectionListener?: () => void, + ): Socket; + /** + * Returns `6` if `input` is an IPv6 address. Returns `4` if `input` is an IPv4 + * address in [dot-decimal notation](https://en.wikipedia.org/wiki/Dot-decimal_notation) with no leading zeroes. Otherwise, returns`0`. + * + * ```js + * net.isIP('::1'); // returns 6 + * net.isIP('127.0.0.1'); // returns 4 + * net.isIP('127.000.000.001'); // returns 0 + * net.isIP('127.0.0.1/24'); // returns 0 + * net.isIP('fhqwhgads'); // returns 0 + * ``` + * @since v0.3.0 + */ + function isIP(input: string): number; + /** + * Returns `true` if `input` is an IPv4 address in [dot-decimal notation](https://en.wikipedia.org/wiki/Dot-decimal_notation) with no + * leading zeroes. Otherwise, returns `false`. + * + * ```js + * net.isIPv4('127.0.0.1'); // returns true + * net.isIPv4('127.000.000.001'); // returns false + * net.isIPv4('127.0.0.1/24'); // returns false + * net.isIPv4('fhqwhgads'); // returns false + * ``` + * @since v0.3.0 + */ + function isIPv4(input: string): boolean; + /** + * Returns `true` if `input` is an IPv6 address. Otherwise, returns `false`. + * + * ```js + * net.isIPv6('::1'); // returns true + * net.isIPv6('fhqwhgads'); // returns false + * ``` + * @since v0.3.0 + */ + function isIPv6(input: string): boolean; + // interface SocketAddressInitOptions { + // /** + // * The network address as either an IPv4 or IPv6 string. + // * @default 127.0.0.1 + // */ + // address?: string | undefined; + // /** + // * @default `'ipv4'` + // */ + // family?: IPVersion | undefined; + // /** + // * An IPv6 flow-label used only if `family` is `'ipv6'`. + // * @default 0 + // */ + // flowlabel?: number | undefined; + // /** + // * An IP port. + // * @default 0 + // */ + // port?: number | undefined; + // } + /** + * @since v15.14.0, v14.18.0 + */ + // class SocketAddress { + // constructor(options: SocketAddressInitOptions); + // /** + // * @since v15.14.0, v14.18.0 + // */ + // readonly address: string; + // /** + // * Either \`'ipv4'\` or \`'ipv6'\`. + // * @since v15.14.0, v14.18.0 + // */ + // readonly family: IPVersion; + // /** + // * @since v15.14.0, v14.18.0 + // */ + // readonly port: number; + // /** + // * @since v15.14.0, v14.18.0 + // */ + // readonly flowlabel: number; + // } +} +declare module "node:net" { + export * from "net"; +} diff --git a/packages/bun-types/package.json b/packages/bun-types/package.json index b8c8a2e2a..b7eea6ea5 100644 --- a/packages/bun-types/package.json +++ b/packages/bun-types/package.json @@ -6,13 +6,11 @@ "scripts": { "build": "rm -rf ./dist && bun run bundle && bun run fmt", "bundle": "bun scripts/bundle.ts ./dist", - "docs": "bun run build && typedoc", "test": "tsd", "fmt": "prettier --write './**/*.{ts,tsx,js,jsx}'" }, "devDependencies": { "tsd": "^0.22.0", - "typedoc": "^0.23.9", "prettier": "^2.4.1" }, "tsd": { diff --git a/packages/bun-types/string_decoder.d.ts b/packages/bun-types/string_decoder.d.ts index aee1680dc..b4d507fac 100644 --- a/packages/bun-types/string_decoder.d.ts +++ b/packages/bun-types/string_decoder.d.ts @@ -39,6 +39,7 @@ * @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/string_decoder.js) */ declare module "string_decoder" { + import { ArrayBufferView } from "bun"; class StringDecoder { constructor(encoding?: BufferEncoding); /** @@ -47,7 +48,7 @@ declare module "string_decoder" { * returned string and stored in an internal buffer for the next call to`stringDecoder.write()` or `stringDecoder.end()`. * @param buffer A `Buffer`, or `TypedArray`, or `DataView` containing the bytes to decode. */ - write(buffer: Buffer | ArrayBufferView): string; + write(buffer: ArrayBufferView): string; /** * Returns any remaining input stored in the internal buffer as a string. Bytes * representing incomplete UTF-8 and UTF-16 characters will be replaced with @@ -57,7 +58,7 @@ declare module "string_decoder" { * After `end()` is called, the `stringDecoder` object can be reused for new input. * @param buffer A `Buffer`, or `TypedArray`, or `DataView` containing the bytes to decode. */ - end(buffer?: Buffer | ArrayBufferView): string; + end(buffer?: ArrayBufferView): string; } } declare module "node:string_decoder" { diff --git a/packages/bun-types/tsconfig.json b/packages/bun-types/tsconfig.json index 709470196..644fb4587 100644 --- a/packages/bun-types/tsconfig.json +++ b/packages/bun-types/tsconfig.json @@ -10,6 +10,7 @@ "moduleResolution": "node", "allowSyntheticDefaultImports": true, "disableSolutionSearching": true, + "noUnusedLocals": true }, "exclude": [ "dist", diff --git a/packages/bun-types/zlib.d.ts b/packages/bun-types/zlib.d.ts index 877350b93..ce1e581b0 100644 --- a/packages/bun-types/zlib.d.ts +++ b/packages/bun-types/zlib.d.ts @@ -90,6 +90,7 @@ * @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/zlib.js) */ declare module "zlib" { + import { ArrayBufferView } from "bun"; import * as stream from "node:stream"; interface ZlibOptions { /** diff --git a/src/bun.js/api/bun.zig b/src/bun.js/api/bun.zig index e9b760de5..32bd0a9ec 100644 --- a/src/bun.js/api/bun.zig +++ b/src/bun.js/api/bun.zig @@ -942,7 +942,17 @@ fn doResolve( return null; } - return doResolveWithArgs(ctx, specifier.getZigString(ctx.ptr()), from.getZigString(ctx.ptr()), exception, false); + var is_esm = true; + if (args.nextEat()) |next| { + if (next.isBoolean()) { + is_esm = next.toBoolean(); + } else { + JSC.throwInvalidArguments("esm must be a boolean", .{}, ctx, exception); + return null; + } + } + + return doResolveWithArgs(ctx, specifier.getZigString(ctx.ptr()), from.getZigString(ctx.ptr()), exception, is_esm, false); } fn doResolveWithArgs( @@ -950,6 +960,7 @@ fn doResolveWithArgs( specifier: ZigString, from: ZigString, exception: js.ExceptionRef, + is_esm: bool, comptime is_file_path: bool, ) ?JSC.JSValue { var errorable: ErrorableZigString = undefined; @@ -960,6 +971,7 @@ fn doResolveWithArgs( ctx.ptr(), specifier, from, + is_esm, ); } else { VirtualMachine.resolveForAPI( @@ -967,6 +979,7 @@ fn doResolveWithArgs( ctx.ptr(), specifier, from, + is_esm, ); } @@ -1010,10 +1023,11 @@ export fn Bun__resolve( global: *JSGlobalObject, specifier: JSValue, source: JSValue, + is_esm: bool, ) JSC.JSValue { var exception_ = [1]JSC.JSValueRef{null}; var exception = &exception_; - const value = doResolveWithArgs(global, specifier.getZigString(global), source.getZigString(global), exception, true) orelse { + const value = doResolveWithArgs(global, specifier.getZigString(global), source.getZigString(global), exception, is_esm, true) orelse { return JSC.JSPromise.rejectedPromiseValue(global, JSC.JSValue.fromRef(exception[0])); }; return JSC.JSPromise.resolvedPromiseValue(global, value); @@ -1023,10 +1037,11 @@ export fn Bun__resolveSync( global: *JSGlobalObject, specifier: JSValue, source: JSValue, + is_esm: bool, ) JSC.JSValue { var exception_ = [1]JSC.JSValueRef{null}; var exception = &exception_; - return doResolveWithArgs(global, specifier.getZigString(global), source.getZigString(global), exception, true) orelse { + return doResolveWithArgs(global, specifier.getZigString(global), source.getZigString(global), exception, is_esm, true) orelse { return JSC.JSValue.fromRef(exception[0]); }; } diff --git a/src/bun.js/api/bun/socket.zig b/src/bun.js/api/bun/socket.zig index f4dcfb01c..911dc9e89 100644 --- a/src/bun.js/api/bun/socket.zig +++ b/src/bun.js/api/bun/socket.zig @@ -232,38 +232,65 @@ pub const SocketConfig = struct { } } - if (opts.getTruthy(globalObject, "hostname")) |hostname| { - if (hostname.isEmptyOrUndefinedOrNull() or !hostname.isString()) { - exception.* = JSC.toInvalidArguments("Expected \"hostname\" to be a string", .{}, globalObject).asObjectRef(); - return null; - } + hostname_or_unix: { + if (opts.getTruthy(globalObject, "unix")) |unix_socket| { + if (!unix_socket.isString()) { + exception.* = JSC.toInvalidArguments("Expected \"unix\" to be a string", .{}, globalObject).asObjectRef(); + return null; + } - const port_value = opts.get(globalObject, "port") orelse JSValue.zero; - if (port_value.isEmptyOrUndefinedOrNull() or !port_value.isNumber() or port_value.toInt64() > std.math.maxInt(u16) or port_value.toInt64() < 0) { - exception.* = JSC.toInvalidArguments("Expected \"port\" to be a number between 0 and 65535", .{}, globalObject).asObjectRef(); - return null; - } + hostname_or_unix = unix_socket.getZigString(globalObject).toSlice(bun.default_allocator); - hostname_or_unix = hostname.getZigString(globalObject).toSlice(bun.default_allocator); - port = port_value.toU16(); + if (strings.hasPrefixComptime(hostname_or_unix.slice(), "file://") or strings.hasPrefixComptime(hostname_or_unix.slice(), "unix://") or strings.hasPrefixComptime(hostname_or_unix.slice(), "sock://")) { + hostname_or_unix.ptr += 7; + hostname_or_unix.len -|= 7; + } - if (hostname_or_unix.len == 0) { - exception.* = JSC.toInvalidArguments("Expected \"hostname\" to be a non-empty string", .{}, globalObject).asObjectRef(); - return null; - } - } else if (opts.getTruthy(globalObject, "unix")) |unix_socket| { - if (unix_socket.isEmptyOrUndefinedOrNull() or !unix_socket.isString()) { - exception.* = JSC.toInvalidArguments("Expected \"unix\" to be a string", .{}, globalObject).asObjectRef(); - return null; + if (hostname_or_unix.len > 0) { + break :hostname_or_unix; + } } - hostname_or_unix = unix_socket.getZigString(globalObject).toSlice(bun.default_allocator); + if (opts.getTruthy(globalObject, "hostname")) |hostname| { + if (!hostname.isString()) { + exception.* = JSC.toInvalidArguments("Expected \"hostname\" to be a string", .{}, globalObject).asObjectRef(); + return null; + } + + var port_value = opts.get(globalObject, "port") orelse JSValue.zero; + hostname_or_unix = hostname.getZigString(globalObject).toSlice(bun.default_allocator); + + if (port_value.isEmptyOrUndefinedOrNull() and hostname_or_unix.len > 0) { + const parsed_url = bun.URL.parse(hostname_or_unix.slice()); + if (parsed_url.getPort()) |port_num| { + port_value = JSValue.jsNumber(port_num); + hostname_or_unix.ptr = parsed_url.hostname.ptr; + hostname_or_unix.len = @truncate(u32, parsed_url.hostname.len); + } + } + + if (port_value.isEmptyOrUndefinedOrNull() or !port_value.isNumber() or port_value.toInt64() > std.math.maxInt(u16) or port_value.toInt64() < 0) { + exception.* = JSC.toInvalidArguments("Expected \"port\" to be a number between 0 and 65535", .{}, globalObject).asObjectRef(); + return null; + } + + port = port_value.toU16(); + + if (hostname_or_unix.len == 0) { + exception.* = JSC.toInvalidArguments("Expected \"hostname\" to be a non-empty string", .{}, globalObject).asObjectRef(); + return null; + } + + if (hostname_or_unix.len > 0) { + break :hostname_or_unix; + } + } if (hostname_or_unix.len == 0) { exception.* = JSC.toInvalidArguments("Expected \"unix\" to be a non-empty string", .{}, globalObject).asObjectRef(); return null; } - } else { + exception.* = JSC.toInvalidArguments("Expected either \"hostname\" or \"unix\"", .{}, globalObject).asObjectRef(); return null; } diff --git a/src/bun.js/api/server.zig b/src/bun.js/api/server.zig index f47ee9fc0..137d164f0 100644 --- a/src/bun.js/api/server.zig +++ b/src/bun.js/api/server.zig @@ -2135,8 +2135,8 @@ fn NewRequestContext(comptime ssl_enabled: bool, comptime debug_mode: bool, comp const result = JSC.C.JSObjectCallAsFunctionReturnValue(this.server.globalThis, this.server.config.onError.asObjectRef(), this.server.thisObject.asObjectRef(), 1, &args); if (!result.isEmptyOrUndefinedOrNull()) { - if (result.isError() or result.isAggregateError(this.server.globalThis)) { - this.finishRunningErrorHandler(result, status); + if (result.toError()) |err| { + this.finishRunningErrorHandler(err, status); return; } else if (result.as(Response)) |response| { this.render(response); @@ -2514,7 +2514,10 @@ pub const WebSocketServer = struct { active_connections: usize = 0, /// used by publish() - ssl: bool = false, + flags: packed struct(u2) { + ssl: bool = false, + publish_to_self: bool = true, + } = .{}, pub fn fromJS(globalObject: *JSC.JSGlobalObject, object: JSC.JSValue) ?Handler { var handler = Handler{ .globalObject = globalObject }; @@ -2752,6 +2755,17 @@ pub const WebSocketServer = struct { } } + if (object.get(globalObject, "publishToSelf")) |value| { + if (!value.isUndefinedOrNull()) { + if (!value.isBoolean()) { + globalObject.throwInvalidArguments("websocket expects publishToSelf to be a boolean", .{}); + return null; + } + + server.handler.flags.publish_to_self = value.toBoolean(); + } + } + server.protect(); return server; } @@ -3025,7 +3039,9 @@ pub const ServerWebSocket = struct { log("publish() closed", .{}); return JSValue.jsNumber(0); }; - const ssl = this.handler.ssl; + const flags = this.handler.flags; + const ssl = flags.ssl; + const publish_to_self = flags.publish_to_self; const topic_value = args.ptr[0]; const message_value = args.ptr[1]; @@ -3051,16 +3067,23 @@ pub const ServerWebSocket = struct { return .zero; } - if (message_value.asArrayBuffer(globalThis)) |buffer| { + if (message_value.asArrayBuffer(globalThis)) |array_buffer| { + const buffer = array_buffer.slice(); + if (buffer.len == 0) { globalThis.throw("publish requires a non-empty message", .{}); return .zero; } + const result = if (!publish_to_self) + this.websocket.publish(topic_slice.slice(), buffer, .binary, compress) + else + uws.AnyWebSocket.publishWithOptions(ssl, app, topic_slice.slice(), buffer, .binary, compress); + return JSValue.jsNumber( // if 0, return 0 // else return number of bytes sent - @as(i32, @boolToInt(uws.AnyWebSocket.publishWithOptions(ssl, app, topic_slice.slice(), buffer.slice(), .binary, compress))) * @intCast(i32, @truncate(u31, buffer.len)), + if (result) @intCast(i32, @truncate(u31, buffer.len)) else @as(i32, 0), ); } @@ -3072,10 +3095,16 @@ pub const ServerWebSocket = struct { } const buffer = string_slice.slice(); + + const result = if (!publish_to_self) + this.websocket.publish(topic_slice.slice(), buffer, .text, compress) + else + uws.AnyWebSocket.publishWithOptions(ssl, app, topic_slice.slice(), buffer, .text, compress); + return JSValue.jsNumber( // if 0, return 0 // else return number of bytes sent - @as(i32, @boolToInt(uws.AnyWebSocket.publishWithOptions(ssl, app, topic_slice.slice(), buffer, .text, compress))) * @intCast(i32, @truncate(u31, buffer.len)), + if (result) @intCast(i32, @truncate(u31, buffer.len)) else @as(i32, 0), ); } @@ -3099,7 +3128,9 @@ pub const ServerWebSocket = struct { log("publish() closed", .{}); return JSValue.jsNumber(0); }; - const ssl = this.handler.ssl; + const flags = this.handler.flags; + const ssl = flags.ssl; + const publish_to_self = flags.publish_to_self; const topic_value = args.ptr[0]; const message_value = args.ptr[1]; @@ -3132,10 +3163,16 @@ pub const ServerWebSocket = struct { } const buffer = string_slice.slice(); + + const result = if (!publish_to_self) + this.websocket.publish(topic_slice.slice(), buffer, .text, compress) + else + uws.AnyWebSocket.publishWithOptions(ssl, app, topic_slice.slice(), buffer, .text, compress); + return JSValue.jsNumber( // if 0, return 0 // else return number of bytes sent - @as(i32, @boolToInt(uws.AnyWebSocket.publishWithOptions(ssl, app, topic_slice.slice(), buffer, .text, compress))) * @intCast(i32, @truncate(u31, buffer.len)), + if (result) @intCast(i32, @truncate(u31, buffer.len)) else @as(i32, 0), ); } @@ -3156,7 +3193,9 @@ pub const ServerWebSocket = struct { log("publish() closed", .{}); return JSValue.jsNumber(0); }; - const ssl = this.handler.ssl; + const flags = this.handler.flags; + const ssl = flags.ssl; + const publish_to_self = flags.publish_to_self; const topic_value = args.ptr[0]; const message_value = args.ptr[1]; const compress_value = args.ptr[2]; @@ -3180,19 +3219,25 @@ pub const ServerWebSocket = struct { globalThis.throw("publishBinary requires a non-empty message", .{}); return .zero; } - const buffer = message_value.asArrayBuffer(globalThis) orelse { + const array_buffer = message_value.asArrayBuffer(globalThis) orelse { globalThis.throw("publishBinary expects an ArrayBufferView", .{}); return .zero; }; + const buffer = array_buffer.slice(); if (buffer.len == 0) { return JSC.JSValue.jsNumber(0); } + const result = if (!publish_to_self) + this.websocket.publish(topic_slice.slice(), buffer, .binary, compress) + else + uws.AnyWebSocket.publishWithOptions(ssl, app, topic_slice.slice(), buffer, .binary, compress); + return JSValue.jsNumber( // if 0, return 0 // else return number of bytes sent - @as(i32, @boolToInt(uws.AnyWebSocket.publishWithOptions(ssl, app, topic_slice.slice(), buffer.slice(), .binary, compress))) * @intCast(i32, @truncate(u31, buffer.len)), + if (result) @intCast(i32, @truncate(u31, buffer.len)) else @as(i32, 0), ); } @@ -3200,13 +3245,15 @@ pub const ServerWebSocket = struct { this: *ServerWebSocket, globalThis: *JSC.JSGlobalObject, topic_str: *JSC.JSString, - buffer: *JSC.JSUint8Array, + array: *JSC.JSUint8Array, ) callconv(.C) JSC.JSValue { var app = this.handler.app orelse { log("publish() closed", .{}); return JSValue.jsNumber(0); }; - const ssl = this.handler.ssl; + const flags = this.handler.flags; + const ssl = flags.ssl; + const publish_to_self = flags.publish_to_self; var topic_slice = topic_str.toSlice(globalThis, bun.default_allocator); defer topic_slice.deinit(); @@ -3217,30 +3264,20 @@ pub const ServerWebSocket = struct { const compress = true; - const slice = buffer.slice(); - if (slice.len == 0) { + const buffer = array.slice(); + if (buffer.len == 0) { return JSC.JSValue.jsNumber(0); } + const result = if (!publish_to_self) + this.websocket.publish(topic_slice.slice(), buffer, .binary, compress) + else + uws.AnyWebSocket.publishWithOptions(ssl, app, topic_slice.slice(), buffer, .binary, compress); + return JSValue.jsNumber( // if 0, return 0 // else return number of bytes sent - @as( - i32, - @boolToInt( - uws.AnyWebSocket.publishWithOptions( - ssl, - app, - topic_slice.slice(), - slice, - .binary, - compress, - ), - ), - ) * @intCast( - i32, - @truncate(u31, slice.len), - ), + if (result) @intCast(i32, @truncate(u31, buffer.len)) else @as(i32, 0), ); } @@ -3254,7 +3291,9 @@ pub const ServerWebSocket = struct { log("publish() closed", .{}); return JSValue.jsNumber(0); }; - const ssl = this.handler.ssl; + const flags = this.handler.flags; + const ssl = flags.ssl; + const publish_to_self = flags.publish_to_self; var topic_slice = topic_str.toSlice(globalThis, bun.default_allocator); defer topic_slice.deinit(); @@ -3266,24 +3305,21 @@ pub const ServerWebSocket = struct { const compress = true; const slice = str.toSlice(globalThis, bun.default_allocator); - if (slice.len == 0) { + defer slice.deinit(); + const buffer = slice.slice(); + + if (buffer.len == 0) { return JSC.JSValue.jsNumber(0); } + const result = if (!publish_to_self) + this.websocket.publish(topic_slice.slice(), buffer, .text, compress) + else + uws.AnyWebSocket.publishWithOptions(ssl, app, topic_slice.slice(), buffer, .text, compress); return JSValue.jsNumber( // if 0, return 0 // else return number of bytes sent - @as(i32, @boolToInt(uws.AnyWebSocket.publishWithOptions( - ssl, - app, - topic_slice.slice(), - slice.slice(), - .text, - compress, - ))) * @intCast( - i32, - @truncate(u31, slice.len), - ), + if (result) @intCast(i32, @truncate(u31, buffer.len)) else @as(i32, 0), ); } @@ -4107,7 +4143,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { } if (new_config.websocket) |*ws| { - ws.handler.ssl = ssl_enabled; + ws.handler.flags.ssl = ssl_enabled; if (ws.handler.onMessage != .zero or ws.handler.onOpen != .zero) { if (this.config.websocket) |old_ws| { old_ws.unprotect(); @@ -4671,7 +4707,7 @@ pub fn NewServer(comptime ssl_enabled_: bool, comptime debug_mode_: bool) type { if (this.config.websocket) |*websocket| { websocket.globalObject = this.globalThis; websocket.handler.app = this.app; - websocket.handler.ssl = ssl_enabled; + websocket.handler.flags.ssl = ssl_enabled; this.app.ws( "/*", this, diff --git a/src/bun.js/bindings/ImportMetaObject.cpp b/src/bun.js/bindings/ImportMetaObject.cpp index 5838bde04..0247b5140 100644 --- a/src/bun.js/bindings/ImportMetaObject.cpp +++ b/src/bun.js/bindings/ImportMetaObject.cpp @@ -80,7 +80,7 @@ static EncodedJSValue functionRequireResolve(JSC::JSGlobalObject* globalObject, } } - auto result = Bun__resolveSync(globalObject, JSC::JSValue::encode(moduleName), from); + auto result = Bun__resolveSync(globalObject, JSC::JSValue::encode(moduleName), from, false); auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); if (!JSC::JSValue::decode(result).isString()) { @@ -156,17 +156,18 @@ JSObject* Zig::ImportMetaObject::createRequireFunction(VM& vm, JSGlobalObject* g JSFunction* requireFunction = JSFunction::create(vm, importMetaObjectRequireCodeGenerator(vm), globalObject); auto clientData = WebCore::clientData(vm); requireFunction->putDirectCustomAccessor(vm, clientData->builtinNames().resolvePublicName(), JSC::CustomGetterSetter::create(vm, functionRequireResolveLazyGetter, functionRequireResolveLazySetter), 0); - requireFunction->putDirect(vm, clientData->builtinNames().pathPrivateName(), jsOwnedString(vm, pathString), JSC::PropertyAttribute::DontEnum | 0); + requireFunction->putDirect(vm, clientData->builtinNames().pathPublicName(), jsString(vm, pathString), JSC::PropertyAttribute::DontEnum | 0); return requireFunction; } extern "C" EncodedJSValue functionImportMeta__resolveSync(JSC::JSGlobalObject* globalObject, JSC::CallFrame* callFrame) { JSC::VM& vm = globalObject->vm(); + auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); switch (callFrame->argumentCount()) { case 0: { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + // not "requires" because "require" could be confusing JSC::throwTypeError(globalObject, scope, "import.meta.resolveSync needs 1 argument (a string)"_s); scope.release(); @@ -176,13 +177,13 @@ extern "C" EncodedJSValue functionImportMeta__resolveSync(JSC::JSGlobalObject* g JSC::JSValue moduleName = callFrame->argument(0); if (moduleName.isUndefinedOrNull()) { - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); JSC::throwTypeError(globalObject, scope, "import.meta.resolveSync expects a string"_s); scope.release(); return JSC::JSValue::encode(JSC::JSValue {}); } JSC__JSValue from; + bool isESM = true; if (callFrame->argumentCount() > 1) { JSC::JSValue fromValue = callFrame->argument(1); @@ -195,8 +196,20 @@ extern "C" EncodedJSValue functionImportMeta__resolveSync(JSC::JSGlobalObject* g fromValue = array->getIndex(globalObject, 0); } } + + if (callFrame->argumentCount() > 2) { + JSC::JSValue isESMValue = callFrame->argument(2); + if (isESMValue.isBoolean()) { + isESM = isESMValue.toBoolean(globalObject); + RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::JSValue {})); + } + } + } else if (fromValue.isBoolean()) { + isESM = fromValue.toBoolean(globalObject); + RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::JSValue {})); } from = JSC::JSValue::encode(fromValue); + } else { JSC::JSObject* thisObject = JSC::jsDynamicCast<JSC::JSObject*>(callFrame->thisValue()); if (UNLIKELY(!thisObject)) { @@ -210,8 +223,7 @@ extern "C" EncodedJSValue functionImportMeta__resolveSync(JSC::JSGlobalObject* g from = JSC::JSValue::encode(thisObject->get(globalObject, clientData->builtinNames().pathPublicName())); } - auto result = Bun__resolveSync(globalObject, JSC::JSValue::encode(moduleName), from); - auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); + auto result = Bun__resolveSync(globalObject, JSC::JSValue::encode(moduleName), from, isESM); if (!JSC::JSValue::decode(result).isString()) { JSC::throwException(globalObject, scope, JSC::JSValue::decode(result)); @@ -266,7 +278,7 @@ JSC_DEFINE_HOST_FUNCTION(functionImportMeta__resolve, from = JSC::JSValue::encode(thisObject->get(globalObject, clientData->builtinNames().pathPublicName())); } - return Bun__resolve(globalObject, JSC::JSValue::encode(moduleName), from); + return Bun__resolve(globalObject, JSC::JSValue::encode(moduleName), from, true); } } } diff --git a/src/bun.js/bindings/ImportMetaObject.h b/src/bun.js/bindings/ImportMetaObject.h index 3ce50ebbb..ff32c85d4 100644 --- a/src/bun.js/bindings/ImportMetaObject.h +++ b/src/bun.js/bindings/ImportMetaObject.h @@ -9,8 +9,8 @@ #include "JSDOMWrapperCache.h" extern "C" JSC_DECLARE_HOST_FUNCTION(functionImportMeta__resolveSync); -extern "C" EncodedJSValue Bun__resolve(JSC::JSGlobalObject* global, JSC::EncodedJSValue specifier, JSC::EncodedJSValue from); -extern "C" EncodedJSValue Bun__resolveSync(JSC::JSGlobalObject* global, JSC::EncodedJSValue specifier, JSC::EncodedJSValue from); +extern "C" EncodedJSValue Bun__resolve(JSC::JSGlobalObject* global, JSC::EncodedJSValue specifier, JSC::EncodedJSValue from, bool is_esm); +extern "C" EncodedJSValue Bun__resolveSync(JSC::JSGlobalObject* global, JSC::EncodedJSValue specifier, JSC::EncodedJSValue from, bool is_esm); namespace Zig { diff --git a/src/bun.js/bindings/JSBufferList.cpp b/src/bun.js/bindings/JSBufferList.cpp index e54b433e5..9b9990598 100644 --- a/src/bun.js/bindings/JSBufferList.cpp +++ b/src/bun.js/bindings/JSBufferList.cpp @@ -32,7 +32,7 @@ void JSBufferList::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* globalObject JSC::PropertyAttribute::DontDelete | JSC::PropertyAttribute::ReadOnly); } -JSC::JSValue JSBufferList::concat(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, int32_t n) +JSC::JSValue JSBufferList::concat(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, size_t n) { auto throwScope = DECLARE_THROW_SCOPE(vm); auto* subclassStructure = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject)->JSBufferSubclassStructure(); @@ -75,6 +75,8 @@ JSC::JSValue JSBufferList::concat(JSC::VM& vm, JSC::JSGlobalObject* lexicalGloba i += length; } + memset(uint8Array->typedVector() + i, 0, n - i); + RELEASE_AND_RETURN(throwScope, uint8Array); } @@ -100,7 +102,7 @@ JSC::JSValue JSBufferList::join(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalO RELEASE_AND_RETURN(throwScope, ropeBuilder.release()); } -JSC::JSValue JSBufferList::consume(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, int32_t n, bool hasString) +JSC::JSValue JSBufferList::consume(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, size_t n, bool hasString) { if (hasString) return _getString(vm, lexicalGlobalObject, n); @@ -108,7 +110,7 @@ JSC::JSValue JSBufferList::consume(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlob return _getBuffer(vm, lexicalGlobalObject, n); } -JSC::JSValue JSBufferList::_getString(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, int32_t total) +JSC::JSValue JSBufferList::_getString(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, size_t total) { auto throwScope = DECLARE_THROW_SCOPE(vm); if (total <= 0 || length() == 0) { @@ -150,13 +152,14 @@ JSC::JSValue JSBufferList::_getString(JSC::VM& vm, JSC::JSGlobalObject* lexicalG if (!ropeBuilder.append(str)) return throwOutOfMemoryError(lexicalGlobalObject, throwScope); m_deque.removeFirst(); - if (n == len) break; + if (n == len) + break; n -= len; } RELEASE_AND_RETURN(throwScope, ropeBuilder.release()); } -JSC::JSValue JSBufferList::_getBuffer(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, int32_t total) +JSC::JSValue JSBufferList::_getBuffer(JSC::VM& vm, JSC::JSGlobalObject* lexicalGlobalObject, size_t total) { auto throwScope = DECLARE_THROW_SCOPE(vm); auto* subclassStructure = reinterpret_cast<Zig::GlobalObject*>(lexicalGlobalObject)->JSBufferSubclassStructure(); @@ -205,16 +208,23 @@ JSC::JSValue JSBufferList::_getBuffer(JSC::VM& vm, JSC::JSGlobalObject* lexicalG auto buffer = array->possiblySharedBuffer(); JSC::JSUint8Array* newArray = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, buffer, n, len - n); iter->set(vm, this, newArray); + offset += n; break; } if (UNLIKELY(!uint8Array->setFromTypedArray(lexicalGlobalObject, offset, array, 0, len, JSC::CopyType::Unobservable))) { return throwOutOfMemoryError(lexicalGlobalObject, throwScope); } m_deque.removeFirst(); - if (n == len) break; + if (n == len) { + offset += len; + break; + } n -= len; offset += len; } + + memset(uint8Array->typedVector() + offset, 0, total - offset); + RELEASE_AND_RETURN(throwScope, uint8Array); } diff --git a/src/bun.js/bindings/JSBufferList.h b/src/bun.js/bindings/JSBufferList.h index a9227e981..94a69c8d1 100644 --- a/src/bun.js/bindings/JSBufferList.h +++ b/src/bun.js/bindings/JSBufferList.h @@ -76,11 +76,11 @@ public: return JSC::JSValue(m_deque.first().get()); } - JSC::JSValue concat(JSC::VM&, JSC::JSGlobalObject*, int32_t); + JSC::JSValue concat(JSC::VM&, JSC::JSGlobalObject*, size_t); JSC::JSValue join(JSC::VM&, JSC::JSGlobalObject*, JSString*); - JSC::JSValue consume(JSC::VM&, JSC::JSGlobalObject*, int32_t, bool); - JSC::JSValue _getBuffer(JSC::VM&, JSC::JSGlobalObject*, int32_t); - JSC::JSValue _getString(JSC::VM&, JSC::JSGlobalObject*, int32_t); + JSC::JSValue consume(JSC::VM&, JSC::JSGlobalObject*, size_t, bool); + JSC::JSValue _getBuffer(JSC::VM&, JSC::JSGlobalObject*, size_t); + JSC::JSValue _getString(JSC::VM&, JSC::JSGlobalObject*, size_t); private: Deque<WriteBarrier<Unknown>> m_deque; @@ -134,6 +134,7 @@ public: // Must be defined for each specialization class. static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); DECLARE_EXPORT_INFO; + private: JSBufferListConstructor(JSC::VM& vm, JSC::Structure* structure, JSC::NativeFunction nativeFunction) : Base(vm, structure, nativeFunction, nativeFunction) diff --git a/src/bun.js/bindings/JSStringDecoder.cpp b/src/bun.js/bindings/JSStringDecoder.cpp index 66f9cb654..ca79e9e1c 100644 --- a/src/bun.js/bindings/JSStringDecoder.cpp +++ b/src/bun.js/bindings/JSStringDecoder.cpp @@ -206,9 +206,30 @@ JSC::JSValue JSStringDecoder::write(JSC::VM& vm, JSC::JSGlobalObject* globalObje } } -JSC::JSValue JSStringDecoder::end(JSC::VM& vm, JSC::JSGlobalObject* globalObject, uint8_t* bufPtr, uint32_t length) +class ResetScope final { +public: + ResetScope(JSStringDecoder* decoder); + ~ResetScope(); + JSStringDecoder* m_decoder; +}; + +ResetScope::ResetScope(JSStringDecoder* decoder) +{ + m_decoder = decoder; +} + +ResetScope::~ResetScope() +{ + m_decoder->m_lastTotal = 0; + m_decoder->m_lastNeed = 0; + memset(m_decoder->m_lastChar, 0, 4); +} + +JSC::JSValue +JSStringDecoder::end(JSC::VM& vm, JSC::JSGlobalObject* globalObject, uint8_t* bufPtr, uint32_t length) { auto throwScope = DECLARE_THROW_SCOPE(vm); + auto resetScope = ResetScope(this); switch (m_encoding) { case BufferEncodingType::ucs2: case BufferEncodingType::utf16le: { diff --git a/src/bun.js/bindings/JSStringDecoder.h b/src/bun.js/bindings/JSStringDecoder.h index 299c2fb96..bce1ae05d 100644 --- a/src/bun.js/bindings/JSStringDecoder.h +++ b/src/bun.js/bindings/JSStringDecoder.h @@ -11,7 +11,10 @@ class JSStringDecoder : public JSC::JSDestructibleObject { public: JSStringDecoder(JSC::VM& vm, JSC::Structure* structure, BufferEncodingType encoding) - : Base(vm, structure), m_lastNeed(0), m_lastTotal(0), m_encoding(encoding) + : Base(vm, structure) + , m_lastNeed(0) + , m_lastTotal(0) + , m_encoding(encoding) { } @@ -108,6 +111,7 @@ public: // Must be defined for each specialization class. static JSC::EncodedJSValue JSC_HOST_CALL_ATTRIBUTES construct(JSC::JSGlobalObject*, JSC::CallFrame*); DECLARE_EXPORT_INFO; + private: JSStringDecoderConstructor(JSC::VM& vm, JSC::Structure* structure, JSC::NativeFunction nativeFunction) : Base(vm, structure, nativeFunction, nativeFunction) diff --git a/src/bun.js/bindings/bindings.zig b/src/bun.js/bindings/bindings.zig index 41a52956d..1bee13fc0 100644 --- a/src/bun.js/bindings/bindings.zig +++ b/src/bun.js/bindings/bindings.zig @@ -1339,7 +1339,7 @@ pub fn NewGlobalObject(comptime Type: type) type { } pub fn resolve(res: *ErrorableZigString, global: *JSGlobalObject, specifier: *ZigString, source: *ZigString) callconv(.C) void { if (comptime @hasDecl(Type, "resolve")) { - @call(.always_inline, Type.resolve, .{ res, global, specifier.*, source.* }); + @call(.always_inline, Type.resolve, .{ res, global, specifier.*, source.*, true }); return; } res.* = ErrorableZigString.err(error.ResolveFailed, ZigString.init(resolveNotImpl).toErrorInstance(global).asVoid()); @@ -1506,7 +1506,7 @@ pub const JSPromise = extern struct { ) JSValue { if (value.isEmpty()) { return resolvedPromiseValue(globalObject, JSValue.jsUndefined()); - } else if (value.isUndefinedOrNull() or !value.isCell()) { + } else if (value.isEmptyOrUndefinedOrNull() or !value.isCell()) { return resolvedPromiseValue(globalObject, value); } @@ -2484,10 +2484,8 @@ pub const JSValue = enum(JSValueReprInt) { } pub fn isObject(this: JSType) bool { - return switch (this) { - .Object, .FinalObject => true, - else => false, - }; + // inline constexpr bool isObjectType(JSType type) { return type >= ObjectType; } + return @enumToInt(this) >= @enumToInt(JSType.Object); } pub fn isFunction(this: JSType) bool { @@ -2663,7 +2661,7 @@ pub const JSValue = enum(JSValueReprInt) { } pub fn isInstanceOf(this: JSValue, global: *JSGlobalObject, constructor: JSValue) bool { - if (this.isEmptyOrUndefinedOrNull()) + if (!this.isCell()) return false; return JSC.C.JSValueIsInstanceOfConstructor(global, this.asObjectRef(), constructor.asObjectRef(), null); @@ -3112,8 +3110,8 @@ pub const JSValue = enum(JSValueReprInt) { pub fn isCustomGetterSetter(this: JSValue) bool { return cppFn("isCustomGetterSetter", .{this}); } - pub fn isObject(this: JSValue) bool { - return cppFn("isObject", .{this}); + pub inline fn isObject(this: JSValue) bool { + return this.isCell() and this.jsType().isObject(); } pub fn isClass(this: JSValue, global: *JSGlobalObject) bool { diff --git a/src/bun.js/bindings/headers-cpp.h b/src/bun.js/bindings/headers-cpp.h index 065fd8caa..a05007fff 100644 --- a/src/bun.js/bindings/headers-cpp.h +++ b/src/bun.js/bindings/headers-cpp.h @@ -1,4 +1,4 @@ -//-- AUTOGENERATED FILE -- 1673374722 +//-- AUTOGENERATED FILE -- 1673376494 // clang-format off #pragma once diff --git a/src/bun.js/bindings/headers.h b/src/bun.js/bindings/headers.h index 4e5dabe4e..71a8d1034 100644 --- a/src/bun.js/bindings/headers.h +++ b/src/bun.js/bindings/headers.h @@ -1,5 +1,5 @@ // clang-format off -//-- AUTOGENERATED FILE -- 1673374722 +//-- AUTOGENERATED FILE -- 1673376494 #pragma once #include <stddef.h> diff --git a/src/bun.js/javascript.zig b/src/bun.js/javascript.zig index fd2ded108..c2c43cf81 100644 --- a/src/bun.js/javascript.zig +++ b/src/bun.js/javascript.zig @@ -429,7 +429,7 @@ pub const VirtualMachine = struct { auto_install_dependencies: bool = false, load_builtins_from_path: []const u8 = "", - onUnhandledRejection: *const fn (*VirtualMachine, globalObject: *JSC.JSGlobalObject, JSC.JSValue) void = defaultOnUnhandledRejection, + onUnhandledRejection: *const OnUnhandledRejection = defaultOnUnhandledRejection, onUnhandledRejectionCtx: ?*anyopaque = null, unhandled_error_counter: usize = 0, @@ -438,6 +438,8 @@ pub const VirtualMachine = struct { gc_controller: JSC.GarbageCollectionController = .{}, + pub const OnUnhandledRejection = fn (*VirtualMachine, globalObject: *JSC.JSGlobalObject, JSC.JSValue) void; + const VMHolder = struct { pub threadlocal var vm: ?*VirtualMachine = null; }; @@ -454,6 +456,30 @@ pub const VirtualMachine = struct { pub threadlocal var is_main_thread_vm: bool = false; + pub const UnhandledRejectionScope = struct { + ctx: ?*anyopaque = null, + onUnhandledRejection: *const OnUnhandledRejection = undefined, + count: usize = 0, + + pub fn apply(this: *UnhandledRejectionScope, vm: *JSC.VirtualMachine) void { + vm.onUnhandledRejection = this.onUnhandledRejection; + vm.onUnhandledRejectionCtx = this.ctx; + vm.unhandled_error_counter = this.count; + } + }; + + pub fn onQuietUnhandledRejectionHandler(this: *VirtualMachine, _: *JSC.JSGlobalObject, _: JSC.JSValue) void { + this.unhandled_error_counter += 1; + } + + pub fn unhandledRejectionScope(this: *VirtualMachine) UnhandledRejectionScope { + return .{ + .onUnhandledRejection = this.onUnhandledRejection, + .ctx = this.onUnhandledRejectionCtx, + .count = this.unhandled_error_counter, + }; + } + pub fn resetUnhandledRejection(this: *VirtualMachine) void { this.onUnhandledRejection = defaultOnUnhandledRejection; } @@ -891,6 +917,7 @@ pub const VirtualMachine = struct { _: *JSGlobalObject, specifier: string, source: string, + is_esm: bool, comptime is_a_file_path: bool, comptime realpath: bool, ) !void { @@ -936,7 +963,7 @@ pub const VirtualMachine = struct { jsc_vm.bundler.fs.top_level_dir, // TODO: do we need to handle things like query string params? if (strings.hasPrefixComptime(specifier, "file://")) specifier["file://".len..] else specifier, - .stmt, + if (is_esm) .stmt else .require, .read_only, )) { .success => |r| r, @@ -1012,19 +1039,53 @@ pub const VirtualMachine = struct { } } - pub fn resolveForAPI(res: *ErrorableZigString, global: *JSGlobalObject, specifier: ZigString, source: ZigString) void { - resolveMaybeNeedsTrailingSlash(res, global, specifier, source, false, true); + pub fn resolveForAPI( + res: *ErrorableZigString, + global: *JSGlobalObject, + specifier: ZigString, + source: ZigString, + is_esm: bool, + ) void { + resolveMaybeNeedsTrailingSlash(res, global, specifier, source, is_esm, false, true); } - pub fn resolveFilePathForAPI(res: *ErrorableZigString, global: *JSGlobalObject, specifier: ZigString, source: ZigString) void { - resolveMaybeNeedsTrailingSlash(res, global, specifier, source, true, true); + pub fn resolveFilePathForAPI( + res: *ErrorableZigString, + global: *JSGlobalObject, + specifier: ZigString, + source: ZigString, + is_esm: bool, + ) void { + resolveMaybeNeedsTrailingSlash(res, global, specifier, source, is_esm, true, true); + } + + pub fn resolve( + res: *ErrorableZigString, + global: *JSGlobalObject, + specifier: ZigString, + source: ZigString, + is_esm: bool, + ) void { + resolveMaybeNeedsTrailingSlash(res, global, specifier, source, is_esm, true, false); } - pub fn resolve(res: *ErrorableZigString, global: *JSGlobalObject, specifier: ZigString, source: ZigString) void { - resolveMaybeNeedsTrailingSlash(res, global, specifier, source, true, false); + fn normalizeSource(source: []const u8) []const u8 { + if (strings.hasPrefixComptime(source, "file://")) { + return source["file://".len..]; + } + + return source; } - pub fn resolveMaybeNeedsTrailingSlash(res: *ErrorableZigString, global: *JSGlobalObject, specifier: ZigString, source: ZigString, comptime is_a_file_path: bool, comptime realpath: bool) void { + pub fn resolveMaybeNeedsTrailingSlash( + res: *ErrorableZigString, + global: *JSGlobalObject, + specifier: ZigString, + source: ZigString, + is_esm: bool, + comptime is_a_file_path: bool, + comptime realpath: bool, + ) void { var result = ResolveFunctionResult{ .path = "", .result = null }; var jsc_vm = VirtualMachine.get(); if (jsc_vm.plugin_runner) |plugin_runner| { @@ -1057,7 +1118,7 @@ pub const VirtualMachine = struct { jsc_vm.bundler.linker.log = old_log; jsc_vm.bundler.resolver.log = old_log; } - _resolve(&result, global, specifier.slice(), source.slice(), is_a_file_path, realpath) catch |err_| { + _resolve(&result, global, specifier.slice(), normalizeSource(source.slice()), is_esm, is_a_file_path, realpath) catch |err_| { var err = err_; const msg: logger.Msg = brk: { var msgs: []logger.Msg = log.msgs.items; diff --git a/src/bun.js/modules/NodeModuleModule.cpp b/src/bun.js/modules/NodeModuleModule.cpp index 02e4e3849..01e061499 100644 --- a/src/bun.js/modules/NodeModuleModule.cpp +++ b/src/bun.js/modules/NodeModuleModule.cpp @@ -19,36 +19,24 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeModuleCreateRequire, return JSC::JSValue::encode(JSC::jsUndefined()); } - Zig::ImportMetaObject *importMetaObject = Zig::ImportMetaObject::create( - globalObject, callFrame->uncheckedArgument(0)); + auto str = callFrame->uncheckedArgument(0).toStringOrNull(globalObject); + RETURN_IF_EXCEPTION(scope, JSC::JSValue::encode(JSC::jsUndefined())); + WTF::String val = str->value(globalObject); + auto *meta = Zig::ImportMetaObject::create(globalObject, str); auto clientData = WebCore::clientData(vm); - - RETURN_IF_EXCEPTION(scope, {}); - - if (!importMetaObject) { - throwTypeError(globalObject, scope, "Invalid path"_s); - return JSC::JSValue::encode(JSC::jsUndefined()); - } - - auto requireFunctionValue = importMetaObject->get( - globalObject, clientData->builtinNames().requirePublicName()); - RETURN_IF_EXCEPTION(scope, {}); - - JSC::JSBoundFunction *boundRequireFunction = JSC::JSBoundFunction::create( - vm, globalObject, requireFunctionValue.getObject(), importMetaObject, - nullptr, 1, jsString(vm, String("require"_s))); - RETURN_IF_EXCEPTION(scope, {}); - auto resolveFunction = importMetaObject->get( - globalObject, clientData->builtinNames().resolveSyncPublicName()); - - JSC::JSBoundFunction *boundResolveFunction = JSC::JSBoundFunction::create( - vm, globalObject, resolveFunction.getObject(), importMetaObject, nullptr, - 1, jsString(vm, String("resolve"_s))); + auto requireFunction = + Zig::ImportMetaObject::createRequireFunction(vm, globalObject, val); + auto nameStr = jsCast<JSFunction *>(requireFunction)->name(vm); + JSC::JSBoundFunction *boundRequireFunction = + JSC::JSBoundFunction::create(vm, globalObject, requireFunction, meta, + nullptr, 0, jsString(vm, nameStr)); boundRequireFunction->putDirect( - vm, clientData->builtinNames().resolvePublicName(), boundResolveFunction, - JSC::PropertyAttribute::Function | 0); + vm, clientData->builtinNames().resolvePublicName(), + requireFunction->getDirect( + vm, clientData->builtinNames().resolvePublicName()), + 0); - RELEASE_AND_RETURN(scope, JSC::JSValue::encode(boundRequireFunction)); + RELEASE_AND_RETURN(scope, JSValue::encode(boundRequireFunction)); } JSC_DEFINE_HOST_FUNCTION(jsFunctionNodeModulePaths, (JSC::JSGlobalObject * globalObject, @@ -113,7 +101,7 @@ JSC_DEFINE_HOST_FUNCTION(jsFunctionResolveFileName, auto result = Bun__resolveSync(globalObject, JSC::JSValue::encode(moduleName), - JSValue::encode(callFrame->argument(1))); + JSValue::encode(callFrame->argument(1)), false); auto scope = DECLARE_THROW_SCOPE(globalObject->vm()); if (!JSC::JSValue::decode(result).isString()) { diff --git a/src/bun.js/net.exports.js b/src/bun.js/net.exports.js index 8d283dd1e..f894cd5fa 100644 --- a/src/bun.js/net.exports.js +++ b/src/bun.js/net.exports.js @@ -86,11 +86,10 @@ export const Socket = (function (InternalSocket) { self.emit("error", error); }, - data(socket, buffer) { - const self = socket.data; - self.bytesRead += buffer.length; + data({ data: self }, { length, buffer }) { + self.bytesRead += length; const queue = self.#readQueue; - const ret = new Buffer(buffer.buffer); + const ret = new Buffer(buffer); if (queue.isEmpty()) { if (self.push(ret)) return; } @@ -197,7 +196,10 @@ export const Socket = (function (InternalSocket) { connect(port, host, connectListener) { // TODO support IPC sockets var path; - if (typeof host == "function") { + if (arguments.length === 1 && typeof port === "string") { + path = port; + port = undefined; + } else if (typeof host == "function") { if (typeof port === "string") { path = port; port = undefined; @@ -336,7 +338,14 @@ export const Socket = (function (InternalSocket) { } else if (this.#writeCallback) { callback(new Error("overlapping _write()")); } else { - if (written > 0) chunk = chunk.slice(written); + if (written > 0) { + if (typeof chunk == "string") { + chunk = chunk.slice(written); + } else { + chunk = chunk.subarray(written); + } + } + this.#writeCallback = callback; this.#writeChunk = chunk; } diff --git a/src/bun.js/test/jest.zig b/src/bun.js/test/jest.zig index 00c8148b2..4b31f7309 100644 --- a/src/bun.js/test/jest.zig +++ b/src/bun.js/test/jest.zig @@ -1124,12 +1124,38 @@ pub const Expect = struct { } const not = this.op.contains(.not); - const result_ = value.call(globalObject, &.{}).toError(); + + const result_: ?JSValue = brk: { + var vm = globalObject.bunVM(); + var scope = vm.unhandledRejectionScope(); + vm.onUnhandledRejection = &VirtualMachine.onQuietUnhandledRejectionHandler; + const return_value: JSValue = value.call(globalObject, &.{}); + + if (return_value.asAnyPromise()) |promise| { + globalObject.bunVM().waitForPromise(promise); + scope.apply(vm); + const promise_result = promise.result(globalObject.vm()); + + switch (promise.status(globalObject.vm())) { + .Fulfilled => { + break :brk null; + }, + .Rejected => { + // since we know for sure it rejected, we should always return the error + break :brk promise_result.toError() orelse promise_result; + }, + .Pending => unreachable, + } + } + scope.apply(vm); + + break :brk return_value.toError(); + }; + const did_throw = result_ != null; const matched_expectation = did_throw == !not; - if (matched_expectation) return thisValue; - if (expected_value.isEmptyOrUndefinedOrNull()) { + if (!matched_expectation) { if (!not) globalObject.throw("Expected function to throw", .{}) else { @@ -1139,7 +1165,14 @@ pub const Expect = struct { return .zero; } - const result = result_.?; + + // If you throw a string, it's treated as the message of an Error + // If you are expected not to throw and you didn't throw, then you pass + // If you are expected to throw a specific message and you throw a different one, then you fail. + if (matched_expectation and (!expected_value.isCell() or not)) + return thisValue; + + const result = result_ orelse JSC.JSValue.jsUndefined(); const expected_error = expected_value.toError(); @@ -1148,7 +1181,10 @@ pub const Expect = struct { if (expected_value.isString()) break :brk expected_value; break :brk expected_error.?.get(globalObject, "message"); }; - const actual = result.get(globalObject, "message"); + const actual: ?JSValue = if (result.isObject()) + result.get(globalObject, "message") + else + null; // TODO support partial match const pass = brk: { if (expected) |expected_message| @@ -1460,7 +1496,6 @@ pub const TestScope = struct { .Internal => vm.waitForPromise(promise), else => {}, } - switch (promise.status(vm.global.vm())) { .Rejected => { vm.runErrorHandler(promise.result(vm.global.vm()), null); @@ -1737,7 +1772,7 @@ pub const DescribeScope = struct { var scope = allocator.create(DescribeScope) catch unreachable; scope.* = .{ .label = (label.toSlice(allocator).cloneIfNeeded(allocator) catch unreachable).slice(), - .parent = this, + .parent = active, .file_id = this.file_id, }; var new_this = DescribeScope.Class.make(ctx, scope); diff --git a/src/bun.zig b/src/bun.zig index 00bceeea4..732862e8f 100644 --- a/src/bun.zig +++ b/src/bun.zig @@ -765,3 +765,4 @@ pub fn zero(comptime Type: type) Type { return @bitCast(Type, out); } pub const c_ares = @import("./deps/c_ares.zig"); +pub const URL = @import("./url.zig").URL; diff --git a/src/cli/test_command.zig b/src/cli/test_command.zig index a36561be0..8fb8bf01b 100644 --- a/src/cli/test_command.zig +++ b/src/cli/test_command.zig @@ -113,7 +113,9 @@ pub const CommandLineReporter = struct { const color_code = comptime if (skip) "<yellow>" else ""; if (Output.enable_ansi_colors_stderr) { - for (scopes) |scope| { + for (scopes) |_, i| { + const index = (scopes.len - 1) - i; + const scope = scopes[index]; if (scope.label.len == 0) continue; writer.writeAll(" ") catch unreachable; @@ -123,7 +125,9 @@ pub const CommandLineReporter = struct { writer.writeAll(" >") catch unreachable; } } else { - for (scopes) |scope| { + for (scopes) |_, i| { + const index = (scopes.len - 1) - i; + const scope = scopes[index]; if (scope.label.len == 0) continue; writer.writeAll(" ") catch unreachable; writer.writeAll(scope.label) catch unreachable; diff --git a/src/deps/uws.zig b/src/deps/uws.zig index 62821c85c..bfcd00e7a 100644 --- a/src/deps/uws.zig +++ b/src/deps/uws.zig @@ -756,10 +756,10 @@ pub const AnyWebSocket = union(enum) { // pub fn iterateTopics(this: AnyWebSocket) { // return uws_ws_iterate_topics(ssl_flag, this.raw(), callback: ?*const fn ([*c]const u8, usize, ?*anyopaque) callconv(.C) void, user_data: ?*anyopaque) void; // } - pub fn publish(this: AnyWebSocket, topic: []const u8, message: []const u8) bool { + pub fn publish(this: AnyWebSocket, topic: []const u8, message: []const u8, opcode: Opcode, compress: bool) bool { return switch (this) { - .ssl => uws_ws_publish(1, this.ssl.raw(), topic.ptr, topic.len, message.ptr, message.len), - .tcp => uws_ws_publish(0, this.tcp.raw(), topic.ptr, topic.len, message.ptr, message.len), + .ssl => uws_ws_publish_with_options(1, this.ssl.raw(), topic.ptr, topic.len, message.ptr, message.len, opcode, compress), + .tcp => uws_ws_publish_with_options(0, this.tcp.raw(), topic.ptr, topic.len, message.ptr, message.len, opcode, compress), }; } pub fn publishWithOptions(ssl: bool, app: *anyopaque, topic: []const u8, message: []const u8, opcode: Opcode, compress: bool) bool { diff --git a/src/js_parser.zig b/src/js_parser.zig index 1fa86041f..1dbf1ebf9 100644 --- a/src/js_parser.zig +++ b/src/js_parser.zig @@ -117,8 +117,9 @@ fn foldStringAddition(lhs: Expr, rhs: Expr) ?Expr { switch (lhs.data) { .e_string => |left| { if (rhs.data == .e_string and left.isUTF8() and rhs.data.e_string.isUTF8()) { - lhs.data.e_string.push(rhs.data.e_string); - return lhs; + var orig = lhs.data.e_string.*; + orig.push(rhs.data.e_string); + return Expr.init(E.String, orig, lhs.loc); } }, .e_binary => |bin| { diff --git a/src/options.zig b/src/options.zig index 024ff188a..b9268f400 100644 --- a/src/options.zig +++ b/src/options.zig @@ -597,6 +597,7 @@ pub const Platform = enum { default_conditions_strings.bun, default_conditions_strings.worker, default_conditions_strings.module, + default_conditions_strings.node, default_conditions_strings.browser, }, ); @@ -606,6 +607,7 @@ pub const Platform = enum { default_conditions_strings.bun, default_conditions_strings.worker, default_conditions_strings.module, + default_conditions_strings.node, default_conditions_strings.browser, }, ); diff --git a/src/runtime.zig b/src/runtime.zig index c942afb13..197dc4264 100644 --- a/src/runtime.zig +++ b/src/runtime.zig @@ -116,7 +116,6 @@ pub const Fallback = struct { }; pub inline fn scriptContent() string { - if (true) return; if (comptime Environment.isDebug) { var dirpath = comptime bun.Environment.base_path ++ std.fs.path.dirname(@src().file).?; var env = std.process.getEnvMap(default_allocator) catch unreachable; @@ -166,7 +165,6 @@ pub const Fallback = struct { fallback: string, entry_point: string, }; - if (true) return; try writer.print(HTMLTemplate, PrintArgs{ .blob = Base64FallbackMessage{ .msg = msg, .allocator = allocator }, .preload = preload, @@ -188,7 +186,6 @@ pub const Fallback = struct { fallback: string, bun_error_page_css: string, }; - if (true) return; try writer.print(HTMLBackendTemplate, PrintArgs{ .blob = Base64FallbackMessage{ .msg = msg, .allocator = allocator }, .bun_error_css = ErrorCSS.sourceContent(), diff --git a/test/bun.js/bun-test/nested-describes.test.ts b/test/bun.js/bun-test/nested-describes.test.ts new file mode 100644 index 000000000..de7ba194e --- /dev/null +++ b/test/bun.js/bun-test/nested-describes.test.ts @@ -0,0 +1,38 @@ +import { +describe, +expect, +test, +} from "bun:test"; + +/* +In this test we want the tests to print out the following on a success. +Each success / fail should show the path of describe and test scopes + +✓ outer most describe > mid describe 1 > inner most describe 1 > first +✓ outer most describe > mid describe 1 > inner most describe 2 > second +✓ outer most describe > mid describe 2 > inner most describe 3 > first + +@TODO add testing for this, would require to read the test console output +*/ + +describe("outer most describe", () => { + describe("mid describe 1", () => { + describe("inner most describe 1", () => { + test("first", () => { + expect(5).toEqual(5); + }) + }) + describe("inner most describe 2", () => { + test("second", () => { + expect(5).toEqual(5); + }) + }) + }) + describe("mid describe 2", () => { + describe("inner most describe 3", () => { + test("third", () => { + expect(5).toEqual(5); + }) + }) + }) +}) diff --git a/test/bun.js/bun-write.test.js b/test/bun.js/bun-write.test.js index fdf31679f..864333ca6 100644 --- a/test/bun.js/bun-write.test.js +++ b/test/bun.js/bun-write.test.js @@ -281,27 +281,21 @@ it("Bun.write(Bun.stderr, 'new TextEncoder().encode(Bun.write STDERR TEST'))", a // FLAKY TEST // Since Bun.file is resolved lazily, this needs to specifically be checked -// it("Bun.write('output.html', HTMLRewriter.transform(Bun.file)))", async (done) => { -// var rewriter = new HTMLRewriter(); +it("Bun.write('output.html', HTMLRewriter.transform(Bun.file)))", async (done) => { + var rewriter = new HTMLRewriter(); -// rewriter.on("div", { -// element(element) { -// element.setInnerContent("<blink>it worked!</blink>", { html: true }); -// }, -// }); -// globalThis["HTMLRewriter.a"] = Bun.write( -// "/tmp/html-rewriter.txt.js", -// "<div>hello</div>", -// ); -// await globalThis["HTMLRewriter.a"]; -// var input = new Response(Bun.file("/tmp/html-rewriter.txt.js")); -// var output = rewriter.transform(input); -// const outpath = `/tmp/html-rewriter.${Date.now()}.html`; -// globalThis["HTMLRewriter.a"] = Bun.write(outpath, output); -// await globalThis["HTMLRewriter.a"]; -// console.log("HIERE"); -// expect(await Bun.file(outpath).text()).toBe( -// "<div><blink>it worked!</blink></div>", -// ); -// done(); -// }); + rewriter.on("div", { + element(element) { + element.setInnerContent("<blink>it worked!</blink>", { html: true }); + }, + }); + await Bun.write("/tmp/html-rewriter.txt.js", "<div>hello</div>"); + var input = new Response(Bun.file("/tmp/html-rewriter.txt.js")); + var output = rewriter.transform(input); + const outpath = `/tmp/html-rewriter.${Date.now()}.html`; + await Bun.write(outpath, output); + expect(await Bun.file(outpath).text()).toBe( + "<div><blink>it worked!</blink></div>", + ); + done(); +}); diff --git a/test/bun.js/child_process-node.test.js b/test/bun.js/child_process-node.test.js index 3cb8cd9e1..41e3e6afc 100644 --- a/test/bun.js/child_process-node.test.js +++ b/test/bun.js/child_process-node.test.js @@ -1,7 +1,6 @@ -import { beforeAll, describe, it as it_ } from "bun:test"; +import { beforeAll, describe, expect, it } from "bun:test"; import { ChildProcess, spawn, exec } from "node:child_process"; import { - strictEqual, throws, assert, createCallCheckCtx, @@ -9,36 +8,7 @@ import { } from "node-test-helpers"; import { tmpdir } from "node:os"; import { gcTick } from "gc"; - -const it = (label, fn) => { - const hasDone = fn.length === 1; - if (fn.constructor.name === "AsyncFunction" && hasDone) { - return it_(label, async (done) => { - gcTick(); - await fn(done); - gcTick(); - }); - } else if (hasDone) { - return it_(label, (done) => { - gcTick(); - fn(done); - gcTick(); - }); - } else if (fn.constructor.name === "AsyncFunction") { - return it_(label, async () => { - gcTick(); - await fn(); - gcTick(); - }); - } else { - return it_(label, () => { - gcTick(); - fn(); - gcTick(); - }); - } -}; - +const strictEqual = (a, b) => expect(a).toStrictEqual(b); const debug = process.env.DEBUG ? console.log : () => {}; const platformTmpDir = require("fs").realpathSync(tmpdir()); diff --git a/test/bun.js/esbuild-child_process.test.ts b/test/bun.js/esbuild-child_process.test.ts index 511779d9f..d64786602 100644 --- a/test/bun.js/esbuild-child_process.test.ts +++ b/test/bun.js/esbuild-child_process.test.ts @@ -1,49 +1,20 @@ -import { transform, transformSync } from "esbuild"; -import { describe, it, expect } from "bun:test"; +import { spawnSync } from "bun"; +import { describe, it, expect, test } from "bun:test"; +import { bunExe } from "bunExe"; -describe("child_process.spawn - esbuild", () => { - it("should transform successfully", async () => { - const result = await transform("console.log('hello world')", { - loader: "js", - target: "node12", - }); - expect(result.code).toBe('console.log("hello world");\n'); - }); +test("esbuild", () => { + const { exitCode, stderr, stdout } = spawnSync( + [bunExe(), import.meta.dir + "/esbuild-test.js"], + { + env: { + BUN_DEBUG_QUIET_LOGS: "1", + }, + }, + ); + const out = "" + stderr?.toString() + stdout?.toString(); + if (exitCode !== 0 && out?.length) { + throw new Error(out); + } - it("works for input exceeding the pipe capacity", async () => { - const hugeString = `console.log(${JSON.stringify("a".repeat(1000000))});`; - - for (let i = 0; i < 2; i++) { - const result = await transform(hugeString, { - loader: "js", - target: "node12", - }); - expect(result.code).toBe(hugeString + "\n"); - } - }); -}); - -describe("child_process.spawnSync - esbuild", () => { - it("should transform successfully", () => { - const result = transformSync("console.log('hello world')", { - loader: "js", - target: "node12", - }); - expect(result.code).toBe('console.log("hello world");\n'); - }); - - // This test is failing with the following error: - // error: Error - // path: "/Users/jarred/Code/bun/test/bun.js/node_modules/esbuild-darwin-arm64/bin/esbuild" - // code: "13" - // syscall: "spawnSync" - // errno: -1 - // it("works for input exceeding the pipe capacity", () => { - // const hugeString = `console.log(${JSON.stringify("a".repeat(100000))});`; - // const result = transformSync(hugeString, { - // loader: "js", - // target: "node12", - // }); - // expect(result.code).toBe(hugeString + "\n"); - // }); + expect(exitCode).toBe(0); }); diff --git a/test/bun.js/esbuild-test.js b/test/bun.js/esbuild-test.js new file mode 100644 index 000000000..beb34b283 --- /dev/null +++ b/test/bun.js/esbuild-test.js @@ -0,0 +1,37 @@ +import { transform, transformSync } from "esbuild"; + +{ + const result = await transform("console.log('hello world')", { + loader: "js", + target: "node12", + }); + if (result.code !== 'console.log("hello world");\n') { + throw new Error("Test failed."); + } +} + +{ + const hugeString = `console.log(${JSON.stringify("a".repeat(1000000))});`; + + for (let i = 0; i < 2; i++) { + const result = await transform(hugeString, { + loader: "js", + target: "node12", + }); + if (result.code !== hugeString + "\n") { + throw new Error("Test failed."); + } + } +} + +{ + const result = transformSync("console.log('hello world')", { + loader: "js", + target: "node12", + }); + if (result.code !== 'console.log("hello world");\n') { + throw new Error("Test failed."); + } +} + +process.exit(0); diff --git a/test/bun.js/fetch.test.js b/test/bun.js/fetch.test.js index ca8e387bf..5d0ca4854 100644 --- a/test/bun.js/fetch.test.js +++ b/test/bun.js/fetch.test.js @@ -1,5 +1,5 @@ -import { it, describe, expect } from "bun:test"; -import fs, { unlinkSync } from "fs"; +import { afterAll, beforeAll, describe, expect, it, test } from "bun:test"; +import fs, { chmodSync, unlinkSync } from "fs"; import { mkfifo } from "mkfifo"; import { gc, withoutAggressiveGC } from "./gc"; @@ -393,6 +393,46 @@ describe("Bun.file", () => { const { size } = Bun.file("/tmp/test-fifo"); expect(size).toBe(Infinity); }); + + function forEachMethod(fn) { + const method = ["arrayBuffer", "text", "json"]; + for (const m of method) { + test(m, fn(m)); + } + } + + describe("bad permissions throws", () => { + beforeAll(async () => { + try { + unlinkSync("/tmp/my-new-file"); + } catch {} + await Bun.write("/tmp/my-new-file", "hey"); + chmodSync("/tmp/my-new-file", 0o000); + }); + afterAll(() => { + try { + unlinkSync("/tmp/my-new-file"); + } catch {} + }); + + forEachMethod((m) => () => { + const file = Bun.file("/tmp/my-new-file"); + expect(async () => await file[m]()).toThrow("Permission denied"); + }); + }); + + describe("non-existent file throws", () => { + beforeAll(() => { + try { + unlinkSync("/tmp/does-not-exist"); + } catch {} + }); + + forEachMethod((m) => async () => { + const file = Bun.file("/tmp/does-not-exist"); + expect(async () => await file[m]()).toThrow("No such file or directory"); + }); + }); }); describe("Blob", () => { diff --git a/test/bun.js/inspect.test.js b/test/bun.js/inspect.test.js index 8789b7aba..738442211 100644 --- a/test/bun.js/inspect.test.js +++ b/test/bun.js/inspect.test.js @@ -2,7 +2,7 @@ import { it, expect, describe } from "bun:test"; it("Blob inspect", () => { expect(Bun.inspect(new Blob(["123"]))).toBe(`Blob (3 bytes)`); - expect(Bun.inspect(new Blob(["123".repeat(900)]))).toBe(`Blob (3 KB)`); + expect(Bun.inspect(new Blob(["123".repeat(900)]))).toBe(`Blob (2.70 KB)`); expect(Bun.inspect(Bun.file("/tmp/file.txt"))) .toBe(`FileRef ("/tmp/file.txt") { type: "text/plain;charset=utf-8" @@ -30,26 +30,25 @@ it("Blob inspect", () => { }`); }); -// this test is currently failing! -// it("utf16 property name", () => { -// var { Database } = require("bun:sqlite"); -// const db = Database.open(":memory:"); -// expect("笑".codePointAt(0)).toBe(31505); +it.skip("utf16 property name", () => { + var { Database } = require("bun:sqlite"); + const db = Database.open(":memory:"); + expect("笑".codePointAt(0)).toBe(31505); -// // latin1 escaping identifier issue -// expect(Object.keys({ 笑: "hey" })[0].codePointAt(0)).toBe(31505); + // latin1 escaping identifier issue + expect(Object.keys({ 笑: "hey" })[0].codePointAt(0)).toBe(31505); -// const output = JSON.stringify( -// [ -// { -// 笑: "😀", -// }, -// ], -// null, -// 2, -// ); -// expect(Bun.inspect(db.prepare("select '😀' as 笑").all())).toBe(output); -// }); + const output = JSON.stringify( + [ + { + 笑: "😀", + }, + ], + null, + 2, + ); + expect(Bun.inspect(db.prepare("select '😀' as 笑").all())).toBe(output); +}); it("latin1", () => { expect(Bun.inspect("English")).toBe("English"); diff --git a/test/bun.js/process.test.js b/test/bun.js/process.test.js index be627b61c..8ea57a28b 100644 --- a/test/bun.js/process.test.js +++ b/test/bun.js/process.test.js @@ -1,6 +1,7 @@ -import { resolveSync } from "bun"; +import { resolveSync, which } from "bun"; import { describe, expect, it } from "bun:test"; import { readFileSync, realpathSync } from "fs"; +import { basename } from "path"; it("process", () => { // this property isn't implemented yet but it should at least return a string @@ -106,11 +107,12 @@ it("process.version starts with v", () => { }); it("process.argv0", () => { - expect(process.argv0).toBe(process.argv[0]); + expect(basename(process.argv0)).toBe(basename(process.argv[0])); }); it("process.execPath", () => { - expect(process.execPath).toBe(realpathSync(process.argv0)); + expect(process.execPath).not.toBe(basename(process.argv0)); + expect(which(process.execPath)).not.toBeNull(); }); it("process.uptime()", () => { diff --git a/test/bun.js/socket/socket.test.ts b/test/bun.js/socket/socket.test.ts index 200f9528c..3c41c96f7 100644 --- a/test/bun.js/socket/socket.test.ts +++ b/test/bun.js/socket/socket.test.ts @@ -25,12 +25,7 @@ it("should keep process alive only when active", async () => { lines.filter(function (line) { return line.startsWith("[Client]"); }), - ).toEqual([ - "[Client] OPENED", - "[Client] GOT response", - "[Client] ENDED", - "[Client] CLOSED", - ]); + ).toEqual(["[Client] OPENED", "[Client] GOT response", "[Client] CLOSED"]); }); it("listen() should throw connection error for invalid host", () => { diff --git a/test/bun.js/transpiler.test.js b/test/bun.js/transpiler.test.js index bc7102b95..94c3d52d2 100644 --- a/test/bun.js/transpiler.test.js +++ b/test/bun.js/transpiler.test.js @@ -915,6 +915,19 @@ export var ComponentThatHasSpreadCausesDeopt = $jsx(Hello, { it("fold string addition", () => { expectPrinted_( + ` +const a = "[^aeiou]"; +const b = a + "[^aeiouy]*"; +console.log(a); + `, + ` +const a = "[^aeiou]"; +const b = a + "[^aeiouy]*"; +console.log(a) + `.trim(), + ); + + expectPrinted_( `export const foo = "a" + "b";`, `export const foo = "ab"`, ); @@ -1734,6 +1747,17 @@ class Foo { `return "foobar";`, ); + check( + ` +const a = "[^aeiou]"; +const b = a + "[^aeiouy]*"; +console.log(a, b); + `, + ` +console.log("[^aeiou]", "[^aeiou][^aeiouy]*"); + `.trim(), + ); + // check that it doesn't inline after "var" check( ` diff --git a/test/bun.js/websocket-server.test.ts b/test/bun.js/websocket-server.test.ts index 1d9c15341..0dc421eb6 100644 --- a/test/bun.js/websocket-server.test.ts +++ b/test/bun.js/websocket-server.test.ts @@ -1,6 +1,6 @@ -import { serve } from "bun"; import { describe, expect, it } from "bun:test"; import { gcTick } from "./gc"; +import { serve } from "bun"; var port = 4321; function getPort() { @@ -49,6 +49,44 @@ describe("websocket server", () => { done(); }); + it("can do publish() with publishToSelf: false", async (done) => { + var server = serve({ + port: getPort(), + websocket: { + open(ws) { + ws.subscribe("all"); + ws.publish("all", "hey"); + server.publish("all", "hello"); + }, + message(ws, msg) { + if (new TextDecoder().decode(msg) !== "hello") { + done(new Error("unexpected message")); + } + }, + close(ws) {}, + publishToSelf: false, + }, + fetch(req, server) { + if (server.upgrade(req)) { + return; + } + + return new Response("success"); + }, + }); + + await new Promise<void>((resolve2, reject2) => { + var socket = new WebSocket(`ws://${server.hostname}:${server.port}`); + + socket.onmessage = (e) => { + expect(e.data).toBe("hello"); + resolve2(); + }; + }); + server.stop(); + done(); + }); + for (let method of ["publish", "publishText", "publishBinary"]) { describe(method, () => { it("in close() should work", async () => { @@ -463,7 +501,9 @@ describe("websocket server", () => { server.stop(); expect(() => { server.upgrade(req); - }).toThrow('To enable websocket support, set the "websocket" object in Bun.serve({})'); + }).toThrow( + 'To enable websocket support, set the "websocket" object in Bun.serve({})', + ); return new Response("success"); }, }); @@ -826,9 +866,11 @@ describe("websocket server", () => { fetch(req) { gcTick(); server.stop(); - if (server.upgrade(req, { - data: { count: 0 }, - })) + if ( + server.upgrade(req, { + data: { count: 0 }, + }) + ) return; return new Response("noooooo hello world"); }, |