Skip to content

Commit b07b469

Browse files
authored
Merge pull request #521 from M4xymm/RDBC-916
RDBC-916 - add Bun runtime support with TLS configuration
2 parents 989452c + cd67194 commit b07b469

File tree

8 files changed

+283
-6
lines changed

8 files changed

+283
-6
lines changed

.github/workflows/BunClient.yml

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
name: tests/bun
2+
3+
on:
4+
push:
5+
branches: [ v7.1 ]
6+
pull_request:
7+
branches: [ v7.1 ]
8+
schedule:
9+
- cron: '0 10 * * *'
10+
workflow_dispatch:
11+
inputs:
12+
ravendb_version:
13+
description: 'RavenDB Version'
14+
required: true
15+
type: string
16+
17+
jobs:
18+
build:
19+
runs-on: ubuntu-latest
20+
21+
env:
22+
RAVENDB_TEST_SERVER_PATH: ./RavenDB/Server/Raven.Server
23+
RAVENDB_TEST_SERVER_CERTIFICATE_PATH: ./certs/server.pfx
24+
RAVENDB_TEST_CLIENT_CERT_PASSPHRASE: client11
25+
RAVENDB_TEST_CLIENT_CERT_PATH: ./certs/nodejs.pem
26+
RAVENDB_TEST_CA_PATH: /usr/local/share/ca-certificates/ca.crt
27+
RAVENDB_TEST_HTTPS_SERVER_URL: https://localhost:8989
28+
RAVENDB_BUILD_TYPE: nightly
29+
RAVEN_License: ${{ secrets.RAVEN_LICENSE }}
30+
RAVENDB_SERVER_VERSION: ${{ matrix.serverVersion }}
31+
32+
strategy:
33+
matrix:
34+
bun-version: [latest]
35+
serverVersion: ["6.2", "7.1", "7.2"]
36+
fail-fast: false
37+
38+
steps:
39+
- uses: actions/checkout@v4.1.4
40+
41+
- name: Setup Bun
42+
uses: oven-sh/setup-bun@v2
43+
with:
44+
bun-version: ${{ matrix.bun-version }}
45+
46+
- name: Download RavenDB Server
47+
run: |
48+
if [[ -n "${{ inputs.ravendb_version }}" ]]; then
49+
wget -O RavenDB.tar.bz2 "https://daily-builds.s3.amazonaws.com/RavenDB-${{ inputs.ravendb_version }}-linux-x64.tar.bz2"
50+
else
51+
wget -O RavenDB.tar.bz2 "https://hibernatingrhinos.com/downloads/RavenDB%20for%20Linux%20x64/latest?buildType=${{ env.RAVENDB_BUILD_TYPE }}&version=${{ matrix.serverVersion }}"
52+
fi
53+
54+
- run: mkdir certs
55+
- run: openssl genrsa -out certs/ca.key 2048
56+
- run: openssl req -new -x509 -key certs/ca.key -out certs/ca.crt -subj "/C=US/ST=Arizona/L=Nevada/O=RavenDB Test CA/OU=RavenDB test CA/CN=localhost/emailAddress=ravendbca@example.com"
57+
- run: openssl genrsa -traditional -out certs/localhost.key 2048
58+
- run: openssl req -new -key certs/localhost.key -out certs/localhost.csr -subj "/C=US/ST=Arizona/L=Nevada/O=RavenDB Test/OU=RavenDB test/CN=localhost/emailAddress=ravendb@example.com" -addext "subjectAltName = DNS:localhost"
59+
- run: openssl x509 -req -extensions ext -extfile test/Assets/test_cert.conf -in certs/localhost.csr -CA certs/ca.crt -CAkey certs/ca.key -CAcreateserial -out certs/localhost.crt
60+
- run: cat certs/localhost.key certs/localhost.crt > certs/nodejs.pem
61+
- run: openssl pkcs12 -passout pass:"" -export -out certs/server.pfx -inkey certs/localhost.key -in certs/localhost.crt
62+
- run: sudo cp certs/ca.crt /usr/local/share/ca-certificates/ca.crt
63+
- run: sudo update-ca-certificates
64+
65+
- name: Extract RavenDB Server
66+
run: tar xjf RavenDB.tar.bz2
67+
68+
- name: Install dependencies
69+
run: bun install
70+
71+
- name: Run Linter
72+
run: bun run lint
73+
74+
- name: Check exports
75+
run: bun run check-exports
76+
77+
- name: Run Tests
78+
run: bun run test

src/Auth/Certificate.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,15 @@ import { throwError } from "../Exceptions/index.js";
44
import { ClientOptions } from "ws";
55
import { Agent } from "undici-types";
66
import { ConnectionOptions } from "node:tls";
7+
import { BunTlsOptions } from "../Types/BunTypes.js";
78

89
export type CertificateType = "pem" | "pfx";
910

1011
export interface ICertificate {
1112
toAgentOptions(): Agent.Options;
1213
toSocketOptions(): ConnectionOptions;
1314
toWebSocketOptions(): ClientOptions;
15+
toBunTlsOptions(): BunTlsOptions;
1416
}
1517

1618
export abstract class Certificate implements ICertificate {
@@ -91,6 +93,14 @@ export abstract class Certificate implements ICertificate {
9193

9294
return {};
9395
}
96+
97+
public toBunTlsOptions(): BunTlsOptions {
98+
if (this._passphrase) {
99+
return { passphrase: this._passphrase };
100+
}
101+
102+
return {};
103+
}
94104
}
95105

96106
export class PemCertificate extends Certificate {
@@ -145,6 +155,16 @@ export class PemCertificate extends Certificate {
145155
});
146156
}
147157

158+
public toBunTlsOptions(): BunTlsOptions {
159+
const options = super.toBunTlsOptions();
160+
return {
161+
...options,
162+
cert: this._certificate,
163+
key: this._key,
164+
ca: this._ca
165+
};
166+
}
167+
148168
protected _fetchPart(token: string): string {
149169
const cert: string = this._certificate as string;
150170
const prefixSuffix: string = "-----";
@@ -203,4 +223,19 @@ export class PfxCertificate extends Certificate {
203223
ca: this._ca
204224
});
205225
}
226+
227+
public toBunTlsOptions(): BunTlsOptions {
228+
const options = super.toBunTlsOptions();
229+
230+
console.warn(
231+
"WARNING: PFX certificates are not currently supported in Bun runtime. " +
232+
"The connection may fail. Please use PEM certificates instead. "
233+
);
234+
235+
return {
236+
...options,
237+
pfx: this._certificate as Buffer,
238+
ca: this._ca
239+
};
240+
}
206241
}

src/Documents/Conventions/DocumentConventions.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { InMemoryDocumentSessionOperations } from "../Session/InMemoryDocumentSe
2020
import { ShardingConventions } from "./ShardingConventions.js";
2121
import { plural } from "../../ext/pluralize/pluralize.js";
2222
import { HttpCompressionAlgorithm } from "../../Http/HttpCompressionAlgorithm.js";
23+
import {RuntimeUtil} from "../../Utility/RuntimeUtil.js";
2324

2425
export type IdConvention = (databaseName: string, entity: object) => Promise<string>;
2526
export type IValueForQueryConverter<T> =
@@ -458,6 +459,10 @@ export class DocumentConventions {
458459
* Can accept compressed HTTP response content and will use decompression methods
459460
*/
460461
public get useHttpDecompression() {
462+
if (RuntimeUtil.isBun() && this._useHttpDecompression === null) {
463+
return false;
464+
}
465+
461466
if (this._useHttpDecompression === null) {
462467
return true;
463468
}

src/Http/RavenCommand.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@ import { DocumentConventions } from "../Documents/Conventions/DocumentConvention
1414
import { ObjectTypeDescriptor } from "../Types/index.js";
1515
import { ObjectUtil } from "../Utility/ObjectUtil.js";
1616
import { Dispatcher } from "undici-types";
17-
import { RequestInit } from "undici";
1817
import { HEADERS } from "../Constants.js";
1918
import { DefaultCommandResponseBehavior } from "./Behaviors/DefaultCommandResponseBehavior.js";
19+
import { RuntimeUtil } from "../Utility/RuntimeUtil.js";
20+
import { BunFetchRequestInit } from "../Types/BunTypes.js";
2021

2122
const log = getLogger({ module: "RavenCommand" });
2223

@@ -154,13 +155,18 @@ export abstract class RavenCommand<TResult> {
154155

155156
log.info(`Send command ${this.constructor.name} to ${uri}${body ? " with body " + body : ""}.`);
156157

157-
if (requestOptions.dispatcher) { // support for fiddler
158-
agent = requestOptions.dispatcher;
159-
}
160-
161158
const bodyToUse = fetcher ? RavenCommand.maybeWrapBody(body) : body;
162159

163-
const optionsToUse = { body: bodyToUse, ...restOptions, dispatcher: agent } as RequestInit;
160+
let optionsToUse: RequestInit | BunFetchRequestInit;
161+
162+
if (RuntimeUtil.isBun()) {
163+
optionsToUse = { body: bodyToUse, ...restOptions } as BunFetchRequestInit;
164+
} else {
165+
if (requestOptions.dispatcher) { // support for fiddler
166+
agent = requestOptions.dispatcher;
167+
}
168+
optionsToUse = { body: bodyToUse, ...restOptions, dispatcher: agent } as RequestInit;
169+
}
164170

165171
const passthrough = new PassThrough();
166172
passthrough.pause();

src/Http/RequestExecutor.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import { Semaphore } from "../Utility/Semaphore.js";
4949
import { Dispatcher, Agent } from "undici-types";
5050
import { EOL } from "../Utility/OsUtil.js";
5151
import { importFix } from "../Utility/ImportUtil.js";
52+
import { RuntimeUtil } from "../Utility/RuntimeUtil.js";
5253

5354
const DEFAULT_REQUEST_OPTIONS = {};
5455

@@ -339,6 +340,10 @@ export class RequestExecutor implements IDisposable {
339340
return null;
340341
}
341342

343+
if (RuntimeUtil.isBun()) {
344+
return null;
345+
}
346+
342347
if (this._httpAgent) {
343348
return this._httpAgent;
344349
}
@@ -1949,6 +1954,10 @@ export class RequestExecutor implements IDisposable {
19491954
this._defaultRequestOptions = Object.assign(
19501955
DEFAULT_REQUEST_OPTIONS,
19511956
this._customHttpRequestOptions);
1957+
1958+
if (RuntimeUtil.isBun() && this._certificate) {
1959+
this._defaultRequestOptions.tls = this._certificate.toBunTlsOptions();
1960+
}
19521961
}
19531962

19541963
public dispose(): void {

src/Primitives/Http.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import { BunTlsOptions } from "../Types/BunTypes.js";
12

23
export type HttpRequestParameters = RequestInit & {
34
uri: string;
45
fetcher?: any;
6+
tls?: BunTlsOptions;
57
};
68
export type HttpRequestParametersWithoutUri = RequestInit & {
79
fetcher?: any;
10+
tls?: BunTlsOptions;
811
};
912
export type HttpResponse = Response;
1013
export type HttpRequest = Request;

src/Types/BunTypes.ts

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/**
2+
* Minimal Bun type definitions for TLS support
3+
* Based on: https://bun.com/docs/guides/http/tls
4+
* https://bun.sh/docs/api/tcp
5+
*/
6+
7+
import {BufferSource} from "node:stream/web";
8+
9+
/**
10+
* TLS options for Bun's fetch API
11+
*/
12+
export interface BunTlsOptions {
13+
/**
14+
* Passphrase for the TLS key
15+
*/
16+
passphrase?: string;
17+
18+
/**
19+
* File path to a .pem file custom Diffie Helman parameters
20+
*/
21+
dhParamsFile?: string;
22+
23+
/**
24+
* Explicitly set a server name
25+
*/
26+
serverName?: string;
27+
28+
/**
29+
* This sets `OPENSSL_RELEASE_BUFFERS` to 1.
30+
* It reduces overall performance but saves some memory.
31+
* @default false
32+
*/
33+
lowMemoryMode?: boolean;
34+
35+
/**
36+
* If set to `false`, any certificate is accepted.
37+
* Default is `$NODE_TLS_REJECT_UNAUTHORIZED` environment variable, or `true` if it is not set.
38+
*/
39+
rejectUnauthorized?: boolean;
40+
41+
/**
42+
* If set to `true`, the server will request a client certificate.
43+
*
44+
* Default is `false`.
45+
*/
46+
requestCert?: boolean;
47+
48+
/**
49+
* Optionally override the trusted CA certificates. Default is to trust
50+
* the well-known CAs curated by Mozilla. Mozilla's CAs are completely
51+
* replaced when CAs are explicitly specified using this option.
52+
*/
53+
ca?: string | BufferSource | Array<string | BufferSource> | undefined;
54+
55+
/**
56+
* Cert chains in PEM format. One cert chain should be provided per
57+
* private key. Each cert chain should consist of the PEM formatted
58+
* certificate for a provided private key, followed by the PEM
59+
* formatted intermediate certificates (if any), in order, and not
60+
* including the root CA (the root CA must be pre-known to the peer,
61+
* see ca). When providing multiple cert chains, they do not have to
62+
* be in the same order as their private keys in key. If the
63+
* intermediate certificates are not provided, the peer will not be
64+
* able to validate the certificate, and the handshake will fail.
65+
*/
66+
cert?: string | BufferSource | Array<string | BufferSource> | undefined;
67+
68+
/**
69+
* Private keys in PEM format. PEM allows the option of private keys
70+
* being encrypted. Encrypted keys will be decrypted with
71+
* options.passphrase. Multiple keys using different algorithms can be
72+
* provided either as an array of unencrypted key strings or buffers,
73+
* or an array of objects in the form {pem: <string|buffer>[,
74+
* passphrase: <string>]}. The object form can only occur in an array.
75+
* object.passphrase is optional. Encrypted keys will be decrypted with
76+
* object.passphrase if provided, or options.passphrase if it is not.
77+
*/
78+
key?: string | BufferSource | Array<string | BufferSource> | undefined;
79+
80+
/**
81+
* Optionally affect the OpenSSL protocol behavior, which is not
82+
* usually necessary. This should be used carefully if at all! Value is
83+
* a numeric bitmask of the SSL_OP_* options from OpenSSL Options
84+
*/
85+
secureOptions?: number | undefined;
86+
87+
/**
88+
* ALPN protocols
89+
*/
90+
ALPNProtocols?: string | BufferSource;
91+
92+
/**
93+
* Cipher suite specification
94+
*/
95+
ciphers?: string;
96+
97+
/**
98+
* Client renegotiation limit
99+
*/
100+
clientRenegotiationLimit?: number;
101+
102+
/**
103+
* Client renegotiation window
104+
*/
105+
clientRenegotiationWindow?: number;
106+
107+
/**
108+
* PFX or PKCS12 encoded certificate and private key
109+
*
110+
* @warning This property is included for Node.js compatibility, but PFX certificates
111+
* are NOT currently supported in Bun runtime. Use PEM certificates (cert/key/ca) instead.
112+
*/
113+
pfx?: Buffer;
114+
}
115+
116+
/**
117+
* Bun-specific fetch request initialization options
118+
* Extend standard RequestInit with Bun-specific TLS options
119+
*/
120+
export interface BunFetchRequestInit extends RequestInit {
121+
/**
122+
* TLS configuration for HTTPS requests
123+
*/
124+
tls?: BunTlsOptions;
125+
}

src/Utility/RuntimeUtil.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/**
2+
* Utility class for detecting runtime environment
3+
*/
4+
export class RuntimeUtil {
5+
private static _isBun: boolean = null;
6+
7+
/**
8+
* Detects if the code is running in Bun runtime
9+
*/
10+
public static isBun(): boolean {
11+
if (this._isBun === null) {
12+
this._isBun = !!process.versions.bun;
13+
}
14+
return this._isBun;
15+
}
16+
}

0 commit comments

Comments
 (0)