Compare commits

...

38 Commits

Author SHA1 Message Date
Claude Bot
ceb5215177 Update WebKit commit and disable bmalloc linking
- Update WebKit commit to 7e9995274c0bd66058770f7768c211459d2e85a5
- Disable bmalloc linking in build configuration as it's now disabled

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-01 09:02:59 +00:00
Jarred Sumner
2bba9b9805 Revert "Try revertng "Simplify mimalloc alignment check (#21497)""
This reverts commit c560840a5c.
2025-08-01 00:23:25 -07:00
Jarred Sumner
a464dbb2ec Update BuildBun.cmake 2025-08-01 00:21:25 -07:00
Jarred Sumner
c560840a5c Try revertng "Simplify mimalloc alignment check (#21497)"
This reverts commit 6afda5b6a3.
2025-08-01 00:03:02 -07:00
Jarred Sumner
316ace2d2b Merge branch 'main' into claude/mimalloc-static-override 2025-07-31 23:29:18 -07:00
Meghan Denny
61e4be8193 node-fallbacks:buffer: fix numberIsNaN ReferenceError (#21527)
fixes https://github.com/oven-sh/bun/issues/21522
2025-07-31 22:48:08 -07:00
Alistair Smith
8dc6279832 More strictly type bun:sqlite transaction functions (#21495)
### What does this PR do?

Fixes #21479

### How did you verify your code works?

bun-types test updated
2025-07-31 22:48:08 -07:00
robobun
0f2e476bca Document static routes vs file routes in HTTP server (#21506)
## Summary
- Add comprehensive documentation distinguishing static routes from file
routes in `Bun.serve()`
- Document caching behavior differences: ETag vs Last-Modified
- Explain performance characteristics and error handling differences
- Provide clear use case recommendations

## Changes
- **File Responses vs Static Responses** section explaining the two
approaches
- **HTTP Caching Behavior** section detailing ETag and Last-Modified
support
- **Status Code Handling** section covering automatic 204/304 responses
- Code examples showing both patterns with clear explanations

## Key Documentation Points
- Static routes (`new Response(await file.bytes())`) buffer content in
memory at startup
- File routes (`new Response(Bun.file(path))`) read from filesystem per
request
- Static routes use ETags for caching, file routes use Last-Modified
headers
- File routes handle 404s automatically, static routes cause startup
errors for missing files
- Both support HTTP caching standards but with different validation
strategies

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-07-31 22:48:08 -07:00
Ciro Spaciari
8bc2449d62 fix(postgres) regression (#21466)
### What does this PR do?
Fix: https://github.com/oven-sh/bun/issues/21351

Relevant changes:
Fix advance to properly cleanup success and failed queries that could be
still be in the queue
Always ref before executing
Use stronger atomics for ref/deref and hasPendingActivity
Fallback when thisValue is freed/null/zero and check if vm is being
shutdown
The bug in --hot in `resolveRopeIfNeeded` Issue is not meant to be fixed
in this PR this is a fix for the postgres regression
Added assertions so this bug is easier to catch on CI
### How did you verify your code works?
Test added

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-07-31 22:48:08 -07:00
pfg
fd45273b5a Readablestreamdefaultcontroller oxlint fix (#21525) 2025-07-31 22:48:08 -07:00
Jarred Sumner
4f8adaa6a2 Delete incorrect assertion in ComptimeStringMap (#21504)
### What does this PR do?

Resolves
```js
Bun v1.2.13 ([64ed68c](64ed68c9e0)) on windows x86_64 [TestCommand]

panic: ComptimeStringMap.fromJS: input is not a string

[comptime_string_map.zig:268](64ed68c9e0/src/comptime_string_map.zig (L268)): getWithEql
[Response.zig:682](64ed68c9e0/src/bun.js/webcore/Response.zig (L682)): init
[Request.zig:679](64ed68c9e0/src/bun.js/webcore/Request.zig (L679)): constructInto
[ZigGeneratedClasses.cpp:37976](64ed68c9e0/C:/buildkite-agent/builds/EC2AMAZ-Q4V5GV4/bun/bun/build/release/codegen/ZigGeneratedClasses.cpp#L37976): WebCore::JSRequestConstructor::construct
2 unknown/js code
llint_entry

Features: tsconfig, Bun.stdout, dotenv, jsc
```

### How did you verify your code works?

There is a test.
2025-07-31 22:48:07 -07:00
pfg
069550e158 Fix "test failing but passed" arrow pointing to the wrong test (#21502)
Before:

```
      failing-test-passes.fixture.ts:
        ^ this test is marked as failing but it passed. Remove \`.failing\` if tested behavior now works
      (fail) This should fail but it doesnt [0.24ms]
        ^ this test is marked as failing but it passed. Remove \`.failing\` if tested behavior now works
      (fail) This should fail but it doesnt (async) [0.23ms]
```

After:

```
      failing-test-passes.fixture.ts:
      (fail) This should fail but it doesnt [0.24ms]
        ^ this test is marked as failing but it passed. Remove \`.failing\` if tested behavior now works
      (fail) This should fail but it doesnt (async) [0.23ms]
        ^ this test is marked as failing but it passed. Remove \`.failing\` if tested behavior now works
```

Adds a snapshot test

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-07-31 22:48:07 -07:00
Jarred Sumner
8d1f69650c Fixes "Stream is already ended" error when cancelling a request (#21481)
### What does this PR do?

if you spam the refresh button in `next dev`, we print this error:
```
 ⨯ Error: Stream is already ended
    at writeHead (null)
    at <anonymous> (null)
    at <anonymous> (null)
    at <anonymous> (null)
    at <anonymous> (null)
    at <anonymous> (null)
    at <anonymous> (null)
    at <anonymous> (null)
    at processTicksAndRejections (null) {
  digest: '2259044225',
  code: 'ERR_STREAM_ALREADY_FINISHED',
  toString: [Function: toString]
}
 ⨯ Error: failed to pipe response
    at processTicksAndRejections (unknown:7:39) {
  [cause]: Error: Stream is already ended
      at writeHead (null)
      at <anonymous> (null)
      at <anonymous> (null)
      at <anonymous> (null)
      at <anonymous> (null)
      at <anonymous> (null)
      at <anonymous> (null)
      at <anonymous> (null)
      at processTicksAndRejections (null) {
    digest: '2259044225',
    code: 'ERR_STREAM_ALREADY_FINISHED',
    toString: [Function: toString]
  }
}
 ⨯ Error: failed to pipe response
    at processTicksAndRejections (unknown:7:39) {
  page: '/',
  [cause]: Error: Stream is already ended
      at writeHead (null)
      at <anonymous> (null)
      at <anonymous> (null)
      at <anonymous> (null)
      at <anonymous> (null)
      at <anonymous> (null)
      at <anonymous> (null)
      at <anonymous> (null)
      at processTicksAndRejections (null) {
    digest: '2259044225',
    code: 'ERR_STREAM_ALREADY_FINISHED',
    toString: [Function: toString]
  }
}
```

If the socket is already closed when writeHead is called, we're supposed
to silently ignore it instead of throwing an error . The close event is
supposed to be emitted on the next tick. Now, I think there are also
cases where we do not emit the close event which is similarly bad.

### How did you verify your code works?

Need to go through the node http server tests and see if any new ones
pass. Also maybe some will fail on this PR, let's see.
2025-07-31 22:48:07 -07:00
Zack Radisic
0ce427f073 Fix assertion failure on Windows in resolver (#21510)
### What does this PR do?

We had `bun.strings.assertIsValidWindowsPath(...)` in the resolver, but
we can't do this because the path may come from the user. Instead, let
our error handling code handle it.

Also fixes #21065

---------

Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
2025-07-31 22:48:07 -07:00
Meghan Denny
8e4b5d51b3 test: handle docker exiting with a signal (#21512) 2025-07-31 22:48:07 -07:00
fuyou
5f010c65d8 fix(mock): add support for rejected values in JSMockFunction (#21489) 2025-07-31 22:48:07 -07:00
Zack Radisic
2d6ce0ee61 Fix lldb pretty printing for bun.collections.MultiArrayList(...) (#21499)
### What does this PR do?

We have our own `MultiArrayList(...)` in
`src/collections/multi_array_list.zig` (is this really necessary?) and
this does not work with the existing lldb pretty printing functions
because they are under a different symbol name:
`collections.multi_array_list.MultiArrayList*` instead of
`multi_array_list.MultiArrayList*`
2025-07-31 22:48:07 -07:00
taylor.fish
c6da7496d8 Resync MultiArrayList with Zig standard library (#21500)
(For internal tracking: fixes STAB-912)
2025-07-31 22:48:07 -07:00
Dylan Conway
b8f5ba950e sync webkit (#21436)
### What does this PR do?

<!-- **Please explain what your changes do**, example: -->

<!--

This adds a new flag --bail to bun test. When set, it will stop running
tests after the first failure. This is useful for CI environments where
you want to fail fast.

-->

### How did you verify your code works?
ran fuzzy-wuzzy.test.ts
<!-- **For code changes, please include automated tests**. Feel free to
uncomment the line below -->

<!-- I wrote automated tests -->

<!-- If JavaScript/TypeScript modules or builtins changed:

- [ ] I included a test for the new code, or existing tests cover it
- [ ] I ran my tests locally and they pass (`bun-debug test
test-file-name.test`)

-->

<!-- If Zig files changed:

- [ ] I checked the lifetime of memory allocated to verify it's (1)
freed and (2) only freed when it should be
- [ ] I included a test for the new code, or an existing test covers it
- [ ] JSValue used outside of the stack is either wrapped in a
JSC.Strong or is JSValueProtect'ed
- [ ] I wrote TypeScript/JavaScript tests and they pass locally
(`bun-debug test test-file-name.test`)
-->

<!-- If new methods, getters, or setters were added to a publicly
exposed class:

- [ ] I added TypeScript types for the new methods, getters, or setters
-->

<!-- If dependencies in tests changed:

- [ ] I made sure that specific versions of dependencies are used
instead of ranged or tagged versions
-->

<!-- If a new builtin ESM/CJS module was added:

- [ ] I updated Aliases in `module_loader.zig` to include the new module
- [ ] I added a test that imports the module
- [ ] I added a test that require() the module
-->
2025-07-31 22:48:06 -07:00
taylor.fish
6afda5b6a3 Simplify mimalloc alignment check (#21497)
This slightly reduces memory use.

Maximum memory use of `bun test html-rewriter`, averaged across 100
iterations:

* 101975 kB without this change
* 101634 kB with this change

I also tried changing the code to always use the aligned allocation
functions, but this slightly increased memory use, to 102160 kB.

(For internal tracking: fixes ENG-19866)
2025-07-31 22:48:06 -07:00
taylor.fish
f54d315e9e Add helper type to detect unsynchronized concurrent accesses of shared data (#21476)
Add a helper type to help detect race conditions. There's no performance
or memory use penalty in release builds.

Actually adding the type to various places will be left for future PRs.

(For internal tracking: fixes STAB-852)
2025-07-31 22:48:06 -07:00
Kai Tamkun
df10aad29d Replace allocator isNull() check with an assertion in String.toThreadSafeSlice (#21474)
### What does this PR do?

Replaces an if statement with an assertion that the condition is false.
The allocator in question should never be null.

### How did you verify your code works?
2025-07-31 22:48:06 -07:00
Jarred Sumner
3e456d4bb0 Disable findSourceMap when --enable-source-maps is not passed (#21478)
### What does this PR do?

Fixes the error printed:
```js
❯ bun --bun dev
$ next dev --turbopack
   ▲ Next.js 15.4.5 (Turbopack)
   - Local:        http://localhost:3000
   - Network:      http://192.168.1.250:3000

 ✓ Starting...
 ✓ Ready in 637ms
 ○ Compiling / ...
 ✓ Compiled / in 1280ms
/private/tmp/empty/my-app/.next/server/chunks/ssr/[root-of-the-server]__012ba519._.js: Invalid source map. Only conformant source maps can be used to filter stack frames. Cause: TypeError: payload is not an Object. (evaluating '"sections" in payload')
/private/tmp/empty/my-app/.next/server/chunks/ssr/[root-of-the-server]__93bf7db5._.js: Invalid source map. Only conformant source maps can be used to filter stack frames. Cause: TypeError: payload is not an Object. (evaluating '"sections" in payload')
 GET / 200 in 1416ms
^C^[[A
```

### How did you verify your code works?
2025-07-31 22:48:06 -07:00
Meghan Denny
6ca1f7cd48 test: split napi tests into separate files (#21475)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-07-31 22:48:06 -07:00
Zack Radisic
6a1615821e Add asan checks to HiveArray (#21449) 2025-07-31 22:48:05 -07:00
Jarred Sumner
a8921537aa Try mimalloc v3 (#17378)
(For internal tracking: fixes ENG-19852)

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Co-authored-by: Kai Tamkun <kai@tamkun.io>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: taylor.fish <contact@taylor.fish>
2025-07-31 22:48:05 -07:00
pfg
411d1afa2a Fix docs in test.md (#21472) 2025-07-31 22:48:05 -07:00
Ciro Spaciari
e350e90822 fix(net/http2) fix socket internal timeout and owner_symbol check, fix padding support in http2 (#21263)
### What does this PR do?

<!-- **Please explain what your changes do**, example: -->

<!--

This adds a new flag --bail to bun test. When set, it will stop running
tests after the first failure. This is useful for CI environments where
you want to fail fast.

-->

- [ ] Documentation or TypeScript types (it's okay to leave the rest
blank in this case)
- [x] Code changes

### How did you verify your code works?
Tests added for padding support
Timeout of socket is being fired earlier due to backpressure or lack of
precision in usockets timers (now matchs node.js behavior).
Added check for owner_symbol so the error showed in
https://github.com/oven-sh/bun/issues/21055 is handled

<!-- **For code changes, please include automated tests**. Feel free to
uncomment the line below -->

<!-- I wrote automated tests -->

<!-- If JavaScript/TypeScript modules or builtins changed:

- [ ] I included a test for the new code, or existing tests cover it
- [ ] I ran my tests locally and they pass (`bun-debug test
test-file-name.test`)

-->

<!-- If Zig files changed:

- [ ] I checked the lifetime of memory allocated to verify it's (1)
freed and (2) only freed when it should be
- [ ] I included a test for the new code, or an existing test covers it
- [ ] JSValue used outside of the stack is either wrapped in a
JSC.Strong or is JSValueProtect'ed
- [ ] I wrote TypeScript/JavaScript tests and they pass locally
(`bun-debug test test-file-name.test`)
-->

<!-- If new methods, getters, or setters were added to a publicly
exposed class:

- [ ] I added TypeScript types for the new methods, getters, or setters
-->

<!-- If dependencies in tests changed:

- [ ] I made sure that specific versions of dependencies are used
instead of ranged or tagged versions
-->

<!-- If a new builtin ESM/CJS module was added:

- [ ] I updated Aliases in `module_loader.zig` to include the new module
- [ ] I added a test that imports the module
- [ ] I added a test that require() the module
-->

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-07-31 22:48:05 -07:00
Jarred Sumner
8ddf2a10b8 Ensure we handle aborted requests correctly in Rendering API (#21398)
### What does this PR do?

We have to use the existing code for handling aborted requests instead
of immediately calling deinit.

Also made the underlying uws.Response an optional pointer to mark when
the request has already been aborted to make it clear it's no longer
accessible.

### How did you verify your code works?

This needs a test

---------

Co-authored-by: Zack Radisic <56137411+zackradisic@users.noreply.github.com>
2025-07-31 22:48:05 -07:00
Jarred Sumner
44ed5d2d6f Introduce yarn.lock -> bun.lock{b} migrator (#16166)
### What does this PR do?

fixes #6409

This PR implements `bun install` automatic migration from yarn.lock
files to bun.lock, preserving versions exactly. The migration happens
automatically when:

1. A project has a `yarn.lock` file
2. No `bun.lock` or `bun.lockb` file exists  
3. User runs `bun install`

### Current Status:  Complete and Working

The yarn.lock migration feature is **fully functional and
comprehensively tested**. All dependency types are supported:

-  Regular npm dependencies (`package@^1.0.0`)
-  Git dependencies (`git+https://github.com/user/repo.git`,
`github:user/repo`)
-  NPM alias dependencies (`alias@npm:package@version`)
-  File dependencies (`file:./path`)
-  Remote tarball URLs (`https://registry.npmjs.org/package.tgz`)
-  Local tarball files (`file:package.tgz`)

### Test Results

```bash
$ bun bd test test/cli/install/migration/yarn-lock-migration.test.ts
 4 pass, 0 fail
- yarn-lock-mkdirp (basic npm dependency)
- yarn-lock-mkdirp-no-resolved (npm dependency without resolved field)
- yarn-lock-mkdirp-file-dep (file dependency)
- yarn-stuff (all complex dependency types: git, npm aliases, file, remote tarballs)
```

### How did you verify your code works?

1. **Comprehensive test suite**: Added 4 test cases covering all
dependency types
2. **Version preservation**: Verified that package versions are
preserved exactly during migration
3. **Real-world scenarios**: Tested with complex yarn.lock files
containing git deps, npm aliases, file deps, and remote tarballs
4. **Migration logging**: Confirms migration with log message `[X.XXms]
migrated lockfile from yarn.lock`

### Key Implementation Details

- **Core parser**: `src/install/yarn.zig` handles all yarn.lock parsing
and dependency type resolution
- **Integration**: Migration is built into existing lockfile loading
infrastructure
- **Performance**: Migration typically completes in ~1ms for most
projects
- **Compatibility**: Preserves exact dependency versions and resolution
behavior

The implementation correctly handles edge cases like npm aliases, git
dependencies with commits, file dependencies with transitive deps, and
remote tarballs.

---------

Co-authored-by: Jarred-Sumner <709451+Jarred-Sumner@users.noreply.github.com>
Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: RiskyMH <git@riskymh.dev>
Co-authored-by: RiskyMH <56214343+RiskyMH@users.noreply.github.com>
2025-07-31 22:48:05 -07:00
Jarred Sumner
2f35945012 Update BuildMimalloc.cmake 2025-07-31 22:31:22 -07:00
Meghan Denny
2a6d018d73 node-fallbacks:buffer: fix numberIsNaN ReferenceError (#21527)
fixes https://github.com/oven-sh/bun/issues/21522
2025-07-31 22:07:17 -07:00
Alistair Smith
8efe7945eb More strictly type bun:sqlite transaction functions (#21495)
### What does this PR do?

Fixes #21479

### How did you verify your code works?

bun-types test updated
2025-07-31 22:06:55 -07:00
robobun
5bdcf339d7 Document static routes vs file routes in HTTP server (#21506)
## Summary
- Add comprehensive documentation distinguishing static routes from file
routes in `Bun.serve()`
- Document caching behavior differences: ETag vs Last-Modified
- Explain performance characteristics and error handling differences
- Provide clear use case recommendations

## Changes
- **File Responses vs Static Responses** section explaining the two
approaches
- **HTTP Caching Behavior** section detailing ETag and Last-Modified
support
- **Status Code Handling** section covering automatic 204/304 responses
- Code examples showing both patterns with clear explanations

## Key Documentation Points
- Static routes (`new Response(await file.bytes())`) buffer content in
memory at startup
- File routes (`new Response(Bun.file(path))`) read from filesystem per
request
- Static routes use ETags for caching, file routes use Last-Modified
headers
- File routes handle 404s automatically, static routes cause startup
errors for missing files
- Both support HTTP caching standards but with different validation
strategies

🤖 Generated with [Claude Code](https://claude.ai/code)

---------

Co-authored-by: Claude Bot <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-07-31 22:06:35 -07:00
Ciro Spaciari
03afe6ef28 fix(postgres) regression (#21466)
### What does this PR do?
Fix: https://github.com/oven-sh/bun/issues/21351

Relevant changes:
Fix advance to properly cleanup success and failed queries that could be
still be in the queue
Always ref before executing
Use stronger atomics for ref/deref and hasPendingActivity
Fallback when thisValue is freed/null/zero and check if vm is being
shutdown
The bug in --hot in `resolveRopeIfNeeded` Issue is not meant to be fixed
in this PR this is a fix for the postgres regression
Added assertions so this bug is easier to catch on CI
### How did you verify your code works?
Test added

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-07-31 16:26:35 -07:00
pfg
ce5152dd7a Readablestreamdefaultcontroller oxlint fix (#21525) 2025-07-31 15:05:42 -07:00
autofix-ci[bot]
b03a6344d8 [autofix.ci] apply automated fixes 2025-07-30 06:17:18 +00:00
Claude Bot
8b2e19391c Add WebSocket Blob support
- Uncommented and implemented blob sending in JSWebSocket.cpp
- Added blob handling for send(), ping(), and pong() methods
- Implemented blob data access functions in Blob.zig
- Added writeBlob function to WebSocket Zig client
- Added comprehensive tests for WebSocket blob functionality
- Enabled blob binaryType support in WebSocket

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-30 06:14:01 +00:00
17 changed files with 751 additions and 171 deletions

View File

@@ -45,6 +45,7 @@ endif()
# --- Dependencies ---
set(BUN_DEPENDENCIES
Mimalloc # must be first
BoringSSL
Brotli
Cares
@@ -52,7 +53,6 @@ set(BUN_DEPENDENCIES
LibDeflate
LolHtml
Lshpack
Mimalloc
TinyCC
Zlib
LibArchive # must be loaded after zlib
@@ -1112,9 +1112,10 @@ else()
${WEBKIT_LIB_PATH}/libWTF.a
${WEBKIT_LIB_PATH}/libJavaScriptCore.a
)
if(NOT APPLE OR EXISTS ${WEBKIT_LIB_PATH}/libbmalloc.a)
target_link_libraries(${bun} PRIVATE ${WEBKIT_LIB_PATH}/libbmalloc.a)
endif()
# bmalloc is now disabled
# if(NOT APPLE OR EXISTS ${WEBKIT_LIB_PATH}/libbmalloc.a)
# target_link_libraries(${bun} PRIVATE ${WEBKIT_LIB_PATH}/libbmalloc.a)
# endif()
endif()
include_directories(${WEBKIT_INCLUDE_PATH})

View File

@@ -4,7 +4,7 @@ register_repository(
REPOSITORY
oven-sh/mimalloc
COMMIT
c1f17cd2538417620f60bff70bffe7e68d332aec
d0b7c26cdf7bb4104d7d64c7dd05e8f0d5a7d9d4
)
set(MIMALLOC_CMAKE_ARGS
@@ -13,14 +13,26 @@ set(MIMALLOC_CMAKE_ARGS
-DMI_BUILD_SHARED=OFF
-DMI_BUILD_TESTS=OFF
-DMI_USE_CXX=ON
-DMI_OVERRIDE=OFF
-DMI_OSX_ZONE=OFF
-DMI_OSX_INTERPOSE=OFF
-DMI_SKIP_COLLECT_ON_EXIT=ON
)
if(ENABLE_ASAN)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_TRACK_ASAN=ON)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OVERRIDE=OFF)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_ZONE=OFF)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_INTERPOSE=OFF)
else()
if (APPLE OR LINUX)
# Enable static override when ASAN is not enabled
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OVERRIDE=ON)
if(APPLE)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_ZONE=ON)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_INTERPOSE=ON)
else()
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_ZONE=OFF)
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_INTERPOSE=OFF)
endif()
endif()
endif()
if(DEBUG)

View File

@@ -2,7 +2,7 @@ option(WEBKIT_VERSION "The version of WebKit to use")
option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of downloading")
if(NOT WEBKIT_VERSION)
set(WEBKIT_VERSION 642e2252f6298387edb6d2f991a0408fd0320466)
set(WEBKIT_VERSION 7e9995274c0bd66058770f7768c211459d2e85a5)
endif()
string(SUBSTRING ${WEBKIT_VERSION} 0 16 WEBKIT_VERSION_PREFIX)

View File

@@ -164,6 +164,70 @@ Static responses do not allocate additional memory after initialization. You can
Static route responses are cached for the lifetime of the server object. To reload static routes, call `server.reload(options)`.
### File Responses vs Static Responses
When serving files in routes, there are two distinct behaviors depending on whether you buffer the file content or serve it directly:
```ts
Bun.serve({
routes: {
// Static route - content is buffered in memory at startup
"/logo.png": new Response(await Bun.file("./logo.png").bytes()),
// File route - content is read from filesystem on each request
"/download.zip": new Response(Bun.file("./download.zip")),
},
});
```
**Static routes** (`new Response(await file.bytes())`) buffer content in memory at startup:
- **Zero filesystem I/O** during requests - content served entirely from memory
- **ETag support** - Automatically generates and validates ETags for caching
- **If-None-Match** - Returns `304 Not Modified` when client ETag matches
- **No 404 handling** - Missing files cause startup errors, not runtime 404s
- **Memory usage** - Full file content stored in RAM
- **Best for**: Small static assets, API responses, frequently accessed files
**File routes** (`new Response(Bun.file(path))`) read from filesystem per request:
- **Filesystem reads** on each request - checks file existence and reads content
- **Built-in 404 handling** - Returns `404 Not Found` if file doesn't exist or becomes inaccessible
- **Last-Modified support** - Uses file modification time for `If-Modified-Since` headers
- **If-Modified-Since** - Returns `304 Not Modified` when file hasn't changed since client's cached version
- **Range request support** - Automatically handles partial content requests with `Content-Range` headers
- **Streaming transfers** - Uses buffered reader with backpressure handling for efficient memory usage
- **Memory efficient** - Only buffers small chunks during transfer, not entire file
- **Best for**: Large files, dynamic content, user uploads, files that change frequently
### HTTP Caching Behavior
Both route types implement HTTP caching standards but with different strategies:
#### Static Routes Caching
- **ETag generation**: Automatically computes ETag hash from content at startup
- **If-None-Match**: Validates client ETag against server ETag
- **304 responses**: Returns `304 Not Modified` with empty body when ETags match
- **Cache headers**: Inherits any `Cache-Control` headers you provide in the Response
- **Consistency**: ETag remains constant until server restart or route reload
#### File Routes Caching
- **Last-Modified**: Uses file's `mtime` for `Last-Modified` header
- **If-Modified-Since**: Compares client date with file modification time
- **304 responses**: Returns `304 Not Modified` when file unchanged since client's cached version
- **Content-Length**: Automatically set based on current file size
- **Dynamic validation**: Checks file modification time on each request
#### Status Code Handling
Both route types automatically adjust status codes:
- **200 → 204**: Empty files (0 bytes) return `204 No Content` instead of `200 OK`
- **200 → 304**: Successful cache validation returns `304 Not Modified`
- **File routes only**: Missing or inaccessible files return `404 Not Found`
```ts
const server = Bun.serve({
static: {

View File

@@ -383,19 +383,28 @@ declare module "bun:sqlite" {
* ]);
* ```
*/
transaction(insideTransaction: (...args: any) => void): CallableFunction & {
transaction<A extends any[], T>(
insideTransaction: (...args: A) => T,
): {
/**
* uses "BEGIN DEFERRED"
* Execute the transaction
*/
deferred: (...args: any) => void;
(...args: A): T;
/**
* uses "BEGIN IMMEDIATE"
* Execute the transaction using "BEGIN DEFERRED"
*/
immediate: (...args: any) => void;
deferred: (...args: A) => T;
/**
* uses "BEGIN EXCLUSIVE"
* Execute the transaction using "BEGIN IMMEDIATE"
*/
exclusive: (...args: any) => void;
immediate: (...args: A) => T;
/**
* Execute the transaction using "BEGIN EXCLUSIVE"
*/
exclusive: (...args: A) => T;
};
/**

View File

@@ -925,7 +925,6 @@ pub const String = struct {
pub fn resolveRopeIfNeeded(this: *String, allocator: std.mem.Allocator) void {
if (this.next == null or !this.isUTF8()) return;
var bytes = std.ArrayList(u8).initCapacity(allocator, this.rope_len) catch bun.outOfMemory();
bytes.appendSliceAssumeCapacity(this.data);
var str = this.next;
while (str) |part| {

19
src/js/builtins.d.ts vendored
View File

@@ -65,7 +65,15 @@ declare function $extractHighWaterMarkFromQueuingStrategyInit(obj: any): any;
* Overrides **
*/
interface ReadableStreamDefaultController<R = any> extends _ReadableStreamDefaultController<R> {
class ReadableStreamDefaultController<R = any> extends _ReadableStreamDefaultController<R> {
constructor(
stream: unknown,
underlyingSource: unknown,
size: unknown,
highWaterMark: unknown,
$isReadableStream: typeof $isReadableStream,
);
$controlledReadableStream: ReadableStream<R>;
$underlyingSource: UnderlyingSource;
$queue: any;
@@ -605,15 +613,6 @@ declare class OutOfMemoryError {
constructor();
}
declare class ReadableStreamDefaultController {
constructor(
stream: unknown,
underlyingSource: unknown,
size: unknown,
highWaterMark: unknown,
$isReadableStream: typeof $isReadableStream,
);
}
declare class ReadableByteStreamController {
constructor(
stream: unknown,

View File

@@ -357,7 +357,7 @@ function fromObject(obj) {
}
if (obj.length !== undefined) {
if (typeof obj.length !== "number" || numberIsNaN(obj.length)) {
if (typeof obj.length !== "number" || Number.isNaN(obj.length)) {
return createBuffer(0);
}
return fromArrayLike(obj);
@@ -659,7 +659,7 @@ Buffer.prototype.equals = function equals(b) {
Buffer.prototype.inspect = function inspect() {
let str = "";
const max = exports.INSPECT_MAX_BYTES;
const max = INSPECT_MAX_BYTES;
str = this.toString("hex", 0, max)
.replace(/(.{2})/g, "$1 ")
.trim();
@@ -886,7 +886,7 @@ function hexWrite(buf, string, offset, length) {
let i;
for (i = 0; i < length; ++i) {
const parsed = parseInt(string.substr(i * 2, 2), 16);
if (numberIsNaN(parsed)) return i;
if (Number.isNaN(parsed)) return i;
buf[offset + i] = parsed;
}
return i;

View File

@@ -275,6 +275,10 @@ pub fn ThreadSafeRefCount(T: type, field_name: []const u8, destructor: fn (*T) v
return counter.active_counts.load(.seq_cst) == 1;
}
pub fn getCount(counter: *const @This()) u32 {
return counter.active_counts.load(.seq_cst);
}
pub fn dumpActiveRefs(count: *@This()) void {
if (enable_debug) {
const ptr: *T = @alignCast(@fieldParentPtr(field_name, count));

View File

@@ -1,8 +1,8 @@
const PostgresSQLConnection = @This();
const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{});
socket: Socket,
status: Status = Status.connecting,
ref_count: u32 = 1,
ref_count: RefCount = RefCount.init(),
write_buffer: bun.OffsetByteList = .{},
read_buffer: bun.OffsetByteList = .{},
@@ -15,7 +15,7 @@ nonpipelinable_requests: u32 = 0,
poll_ref: bun.Async.KeepAlive = .{},
globalObject: *jsc.JSGlobalObject,
vm: *jsc.VirtualMachine,
statements: PreparedStatementsMap,
prepared_statement_id: u64 = 0,
pending_activity_count: std.atomic.Value(u32) = std.atomic.Value(u32).init(0),
@@ -66,6 +66,9 @@ max_lifetime_timer: bun.api.Timer.EventLoopTimer = .{
},
auto_flusher: AutoFlusher = .{},
pub const ref = RefCount.ref;
pub const deref = RefCount.deref;
pub fn onAutoFlush(this: *@This()) bool {
if (this.flags.has_backpressure) {
debug("onAutoFlush: has backpressure", .{});
@@ -95,7 +98,7 @@ fn registerAutoFlusher(this: *PostgresSQLConnection) void {
data_to_send > 0 and // we need data to send
this.status == .connected //and we need to be connected
) {
AutoFlusher.registerDeferredMicrotaskWithTypeUnchecked(@This(), this, this.globalObject.bunVM());
AutoFlusher.registerDeferredMicrotaskWithTypeUnchecked(@This(), this, this.vm);
this.auto_flusher.registered = true;
}
}
@@ -103,7 +106,7 @@ fn registerAutoFlusher(this: *PostgresSQLConnection) void {
fn unregisterAutoFlusher(this: *PostgresSQLConnection) void {
debug("unregisterAutoFlusher registered: {}", .{this.auto_flusher.registered});
if (this.auto_flusher.registered) {
AutoFlusher.unregisterDeferredMicrotaskWithType(@This(), this, this.globalObject.bunVM());
AutoFlusher.unregisterDeferredMicrotaskWithType(@This(), this, this.vm);
this.auto_flusher.registered = false;
}
}
@@ -117,7 +120,7 @@ fn getTimeoutInterval(this: *const PostgresSQLConnection) u32 {
}
pub fn disableConnectionTimeout(this: *PostgresSQLConnection) void {
if (this.timer.state == .ACTIVE) {
this.globalObject.bunVM().timer.remove(&this.timer);
this.vm.timer.remove(&this.timer);
}
this.timer.state = .CANCELLED;
}
@@ -126,14 +129,14 @@ pub fn resetConnectionTimeout(this: *PostgresSQLConnection) void {
if (this.flags.is_processing_data) return;
const interval = this.getTimeoutInterval();
if (this.timer.state == .ACTIVE) {
this.globalObject.bunVM().timer.remove(&this.timer);
this.vm.timer.remove(&this.timer);
}
if (interval == 0) {
return;
}
this.timer.next = bun.timespec.msFromNow(@intCast(interval));
this.globalObject.bunVM().timer.insert(&this.timer);
this.vm.timer.insert(&this.timer);
}
pub fn getQueries(_: *PostgresSQLConnection, thisValue: jsc.JSValue, globalObject: *jsc.JSGlobalObject) bun.JSError!jsc.JSValue {
@@ -192,7 +195,7 @@ fn setupMaxLifetimeTimerIfNecessary(this: *PostgresSQLConnection) void {
if (this.max_lifetime_timer.state == .ACTIVE) return;
this.max_lifetime_timer.next = bun.timespec.msFromNow(@intCast(this.max_lifetime_interval_ms));
this.globalObject.bunVM().timer.insert(&this.max_lifetime_timer);
this.vm.timer.insert(&this.max_lifetime_timer);
}
pub fn onConnectionTimeout(this: *PostgresSQLConnection) bun.api.Timer.EventLoopTimer.Arm {
@@ -254,6 +257,7 @@ pub fn setStatus(this: *PostgresSQLConnection, status: Status) void {
this.status = status;
this.resetConnectionTimeout();
if (this.vm.isShuttingDown()) return;
switch (status) {
.connected => {
@@ -261,7 +265,7 @@ pub fn setStatus(this: *PostgresSQLConnection, status: Status) void {
const js_value = this.js_value;
js_value.ensureStillAlive();
this.globalObject.queueMicrotask(on_connect, &[_]JSValue{ JSValue.jsNull(), js_value });
this.poll_ref.unref(this.globalObject.bunVM());
this.poll_ref.unref(this.vm);
},
else => {},
}
@@ -315,7 +319,7 @@ pub fn failWithJSValue(this: *PostgresSQLConnection, value: JSValue) void {
defer this.refAndClose(value);
const on_close = this.consumeOnCloseCallback(this.globalObject) orelse return;
const loop = this.globalObject.bunVM().eventLoop();
const loop = this.vm.eventLoop();
loop.enter();
defer loop.exit();
_ = on_close.call(
@@ -343,13 +347,21 @@ pub fn fail(this: *PostgresSQLConnection, message: []const u8, err: AnyPostgresE
pub fn onClose(this: *PostgresSQLConnection) void {
this.unregisterAutoFlusher();
var vm = this.globalObject.bunVM();
const loop = vm.eventLoop();
loop.enter();
defer loop.exit();
this.poll_ref.unref(this.globalObject.bunVM());
if (this.vm.isShuttingDown()) {
defer this.updateHasPendingActivity();
this.stopTimers();
if (this.status == .failed) return;
this.fail("Connection closed", error.ConnectionClosed);
this.status = .failed;
this.cleanUpRequests(null);
} else {
const loop = this.vm.eventLoop();
loop.enter();
defer loop.exit();
this.poll_ref.unref(this.vm);
this.fail("Connection closed", error.ConnectionClosed);
}
}
fn sendStartupMessage(this: *PostgresSQLConnection) void {
@@ -392,7 +404,7 @@ fn startTLS(this: *PostgresSQLConnection, socket: uws.AnySocket) void {
pub fn onOpen(this: *PostgresSQLConnection, socket: uws.AnySocket) void {
this.socket = socket;
this.poll_ref.ref(this.globalObject.bunVM());
this.poll_ref.ref(this.vm);
this.updateHasPendingActivity();
if (this.tls_status == .message_sent or this.tls_status == .pending) {
@@ -460,7 +472,9 @@ pub fn onDrain(this: *PostgresSQLConnection) void {
fn drainInternal(this: *PostgresSQLConnection) void {
debug("drainInternal", .{});
const event_loop = this.globalObject.bunVM().eventLoop();
if (this.vm.isShuttingDown()) return this.close();
const event_loop = this.vm.eventLoop();
event_loop.enter();
defer event_loop.exit();
@@ -476,7 +490,7 @@ fn drainInternal(this: *PostgresSQLConnection) void {
pub fn onData(this: *PostgresSQLConnection, data: []const u8) void {
this.ref();
this.flags.is_processing_data = true;
const vm = this.globalObject.bunVM();
const vm = this.vm;
this.disableConnectionTimeout();
defer {
@@ -681,7 +695,7 @@ pub fn call(globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JS
ptr.* = PostgresSQLConnection{
.globalObject = globalObject,
.vm = globalObject.bunVM(),
.database = database,
.user = username,
.password = password,
@@ -764,10 +778,20 @@ fn SocketHandler(comptime ssl: bool) type {
return Socket{ .SocketTCP = s };
}
pub fn onOpen(this: *PostgresSQLConnection, socket: SocketType) void {
if (this.vm.isShuttingDown()) {
@branchHint(.unlikely);
this.close();
return;
}
this.onOpen(_socket(socket));
}
fn onHandshake_(this: *PostgresSQLConnection, _: anytype, success: i32, ssl_error: uws.us_bun_verify_error_t) void {
if (this.vm.isShuttingDown()) {
@branchHint(.unlikely);
this.close();
return;
}
this.onHandshake(success, ssl_error);
}
@@ -785,39 +809,54 @@ fn SocketHandler(comptime ssl: bool) type {
pub fn onConnectError(this: *PostgresSQLConnection, socket: SocketType, _: i32) void {
_ = socket;
if (this.vm.isShuttingDown()) {
@branchHint(.unlikely);
this.close();
return;
}
this.onClose();
}
pub fn onTimeout(this: *PostgresSQLConnection, socket: SocketType) void {
_ = socket;
if (this.vm.isShuttingDown()) {
@branchHint(.unlikely);
this.close();
return;
}
this.onTimeout();
}
pub fn onData(this: *PostgresSQLConnection, socket: SocketType, data: []const u8) void {
_ = socket;
if (this.vm.isShuttingDown()) {
@branchHint(.unlikely);
this.close();
return;
}
this.onData(data);
}
pub fn onWritable(this: *PostgresSQLConnection, socket: SocketType) void {
_ = socket;
if (this.vm.isShuttingDown()) {
@branchHint(.unlikely);
this.close();
return;
}
this.onDrain();
}
};
}
pub fn ref(this: *@This()) void {
bun.assert(this.ref_count > 0);
this.ref_count += 1;
}
pub fn doRef(this: *@This(), _: *jsc.JSGlobalObject, _: *jsc.CallFrame) bun.JSError!JSValue {
this.poll_ref.ref(this.globalObject.bunVM());
this.poll_ref.ref(this.vm);
this.updateHasPendingActivity();
return .js_undefined;
}
pub fn doUnref(this: *@This(), _: *jsc.JSGlobalObject, _: *jsc.CallFrame) bun.JSError!JSValue {
this.poll_ref.unref(this.globalObject.bunVM());
this.poll_ref.unref(this.vm);
this.updateHasPendingActivity();
return .js_undefined;
}
@@ -826,35 +865,29 @@ pub fn doFlush(this: *PostgresSQLConnection, _: *jsc.JSGlobalObject, _: *jsc.Cal
return .js_undefined;
}
pub fn deref(this: *@This()) void {
const ref_count = this.ref_count;
this.ref_count -= 1;
if (ref_count == 1) {
this.disconnect();
this.deinit();
}
fn close(this: *@This()) void {
this.disconnect();
this.unregisterAutoFlusher();
this.write_buffer.deinit(bun.default_allocator);
}
pub fn doClose(this: *@This(), globalObject: *jsc.JSGlobalObject, _: *jsc.CallFrame) bun.JSError!JSValue {
_ = globalObject;
this.disconnect();
this.unregisterAutoFlusher();
this.write_buffer.deinit(bun.default_allocator);
this.close();
return .js_undefined;
}
pub fn stopTimers(this: *PostgresSQLConnection) void {
if (this.timer.state == .ACTIVE) {
this.globalObject.bunVM().timer.remove(&this.timer);
this.vm.timer.remove(&this.timer);
}
if (this.max_lifetime_timer.state == .ACTIVE) {
this.globalObject.bunVM().timer.remove(&this.max_lifetime_timer);
this.vm.timer.remove(&this.max_lifetime_timer);
}
}
pub fn deinit(this: *@This()) void {
this.disconnect();
this.stopTimers();
var iter = this.statements.valueIterator();
while (iter.next()) |stmt_ptr| {
@@ -872,17 +905,7 @@ pub fn deinit(this: *@This()) void {
bun.default_allocator.destroy(this);
}
fn refAndClose(this: *@This(), js_reason: ?jsc.JSValue) void {
// refAndClose is always called when we wanna to disconnect or when we are closed
if (!this.socket.isClosed()) {
// event loop need to be alive to close the socket
this.poll_ref.ref(this.globalObject.bunVM());
// will unref on socket close
this.socket.close();
}
// cleanup requests
fn cleanUpRequests(this: *@This(), js_reason: ?jsc.JSValue) void {
while (this.current()) |request| {
switch (request.status) {
// pending we will fail the request and the stmt will be marked as error ConnectionClosed too
@@ -890,10 +913,12 @@ fn refAndClose(this: *@This(), js_reason: ?jsc.JSValue) void {
const stmt = request.statement orelse continue;
stmt.error_response = .{ .postgres_error = AnyPostgresError.ConnectionClosed };
stmt.status = .failed;
if (js_reason) |reason| {
request.onJSError(reason, this.globalObject);
} else {
request.onError(.{ .postgres_error = AnyPostgresError.ConnectionClosed }, this.globalObject);
if (!this.vm.isShuttingDown()) {
if (js_reason) |reason| {
request.onJSError(reason, this.globalObject);
} else {
request.onError(.{ .postgres_error = AnyPostgresError.ConnectionClosed }, this.globalObject);
}
}
},
// in the middle of running
@@ -901,10 +926,12 @@ fn refAndClose(this: *@This(), js_reason: ?jsc.JSValue) void {
.running,
.partial_response,
=> {
if (js_reason) |reason| {
request.onJSError(reason, this.globalObject);
} else {
request.onError(.{ .postgres_error = AnyPostgresError.ConnectionClosed }, this.globalObject);
if (!this.vm.isShuttingDown()) {
if (js_reason) |reason| {
request.onJSError(reason, this.globalObject);
} else {
request.onError(.{ .postgres_error = AnyPostgresError.ConnectionClosed }, this.globalObject);
}
}
},
// just ignore success and fail cases
@@ -914,6 +941,19 @@ fn refAndClose(this: *@This(), js_reason: ?jsc.JSValue) void {
this.requests.discard(1);
}
}
fn refAndClose(this: *@This(), js_reason: ?jsc.JSValue) void {
// refAndClose is always called when we wanna to disconnect or when we are closed
if (!this.socket.isClosed()) {
// event loop need to be alive to close the socket
this.poll_ref.ref(this.vm);
// will unref on socket close
this.socket.close();
}
// cleanup requests
this.cleanUpRequests(js_reason);
}
pub fn disconnect(this: *@This()) void {
this.stopTimers();
@@ -928,6 +968,7 @@ fn current(this: *PostgresSQLConnection) ?*PostgresSQLQuery {
if (this.requests.readableLength() == 0) {
return null;
}
return this.requests.peekItem(0);
}
@@ -1022,10 +1063,42 @@ pub fn bufferedReader(this: *PostgresSQLConnection) protocol.NewReader(Reader) {
};
}
fn cleanupSuccessQuery(this: *PostgresSQLConnection, item: *PostgresSQLQuery) void {
if (item.flags.simple) {
this.nonpipelinable_requests -= 1;
} else if (item.flags.pipelined) {
this.pipelined_requests -= 1;
} else if (this.flags.waiting_to_prepare) {
this.flags.waiting_to_prepare = false;
}
}
fn advance(this: *PostgresSQLConnection) void {
var offset: usize = 0;
debug("advance", .{});
defer {
while (this.requests.readableLength() > 0) {
const result = this.requests.peekItem(0);
// An item may be in the success or failed state and still be inside the queue (see deinit later comments)
// so we do the cleanup her
switch (result.status) {
.success => {
this.cleanupSuccessQuery(result);
result.deref();
this.requests.discard(1);
continue;
},
.fail => {
result.deref();
this.requests.discard(1);
continue;
},
else => break, // trully current item
}
}
}
while (this.requests.readableLength() > offset and !this.flags.has_backpressure) {
if (this.vm.isShuttingDown()) return this.close();
var req: *PostgresSQLQuery = this.requests.peekItem(offset);
switch (req.status) {
.pending => {
@@ -1084,8 +1157,18 @@ fn advance(this: *PostgresSQLConnection) void {
continue;
},
.prepared => {
const thisValue = req.thisValue.get();
bun.assert(thisValue != .zero);
const thisValue = req.thisValue.tryGet() orelse {
bun.assertf(false, "query value was freed earlier than expected", .{});
if (offset == 0) {
req.deref();
this.requests.discard(1);
} else {
// deinit later
req.status = .fail;
offset += 1;
}
continue;
};
const binding_value = PostgresSQLQuery.js.bindingGetCached(thisValue) orelse .zero;
const columns_value = PostgresSQLQuery.js.columnsGetCached(thisValue) orelse .zero;
req.flags.binary = stmt.fields.len > 0;
@@ -1129,8 +1212,18 @@ fn advance(this: *PostgresSQLConnection) void {
const has_params = stmt.signature.fields.len > 0;
// If it does not have params, we can write and execute immediately in one go
if (!has_params) {
const thisValue = req.thisValue.get();
bun.assert(thisValue != .zero);
const thisValue = req.thisValue.tryGet() orelse {
bun.assertf(false, "query value was freed earlier than expected", .{});
if (offset == 0) {
req.deref();
this.requests.discard(1);
} else {
// deinit later
req.status = .fail;
offset += 1;
}
continue;
};
// prepareAndQueryWithSignature will write + bind + execute, it will change to running after binding is complete
const binding_value = PostgresSQLQuery.js.bindingGetCached(thisValue) orelse .zero;
debug("prepareAndQueryWithSignature", .{});
@@ -1201,13 +1294,7 @@ fn advance(this: *PostgresSQLConnection) void {
return;
},
.success => {
if (req.flags.simple) {
this.nonpipelinable_requests -= 1;
} else if (req.flags.pipelined) {
this.pipelined_requests -= 1;
} else if (this.flags.waiting_to_prepare) {
this.flags.waiting_to_prepare = false;
}
this.cleanupSuccessQuery(req);
if (offset > 0) {
// deinit later
req.status = .fail;
@@ -1242,6 +1329,7 @@ pub fn on(this: *PostgresSQLConnection, comptime MessageType: @Type(.enum_litera
switch (comptime MessageType) {
.DataRow => {
const request = this.current() orelse return error.ExpectedRequest;
var statement = request.statement orelse return error.ExpectedStatement;
var structure: JSValue = .js_undefined;
var cached_structure: ?PostgresCachedStructure = null;
@@ -1297,8 +1385,10 @@ pub fn on(this: *PostgresSQLConnection, comptime MessageType: @Type(.enum_litera
DataCell.Putter.put,
);
}
const thisValue = request.thisValue.get();
bun.assert(thisValue != .zero);
const thisValue = request.thisValue.tryGet() orelse return {
bun.assertf(false, "query value was freed earlier than expected", .{});
return error.ExpectedRequest;
};
const pending_value = PostgresSQLQuery.js.pendingValueGetCached(thisValue) orelse .zero;
pending_value.ensureStillAlive();
const result = putter.toJS(this.globalObject, pending_value, structure, statement.fields_flags, request.flags.result_mode, cached_structure);
@@ -1682,9 +1772,9 @@ pub fn on(this: *PostgresSQLConnection, comptime MessageType: @Type(.enum_litera
pub fn updateRef(this: *PostgresSQLConnection) void {
this.updateHasPendingActivity();
if (this.pending_activity_count.raw > 0) {
this.poll_ref.ref(this.globalObject.bunVM());
this.poll_ref.ref(this.vm);
} else {
this.poll_ref.unref(this.globalObject.bunVM());
this.poll_ref.unref(this.vm);
}
}

View File

@@ -1,5 +1,5 @@
const PostgresSQLQuery = @This();
const RefCount = bun.ptr.ThreadSafeRefCount(@This(), "ref_count", deinit, .{});
statement: ?*PostgresSQLStatement = null,
query: bun.String = bun.String.empty,
cursor_name: bun.String = bun.String.empty,
@@ -8,7 +8,7 @@ thisValue: JSRef = JSRef.empty(),
status: Status = Status.pending,
ref_count: std.atomic.Value(u32) = std.atomic.Value(u32).init(1),
ref_count: RefCount = RefCount.init(),
flags: packed struct(u8) {
is_done: bool = false,
@@ -20,11 +20,11 @@ flags: packed struct(u8) {
_padding: u1 = 0,
} = .{},
pub const ref = RefCount.ref;
pub const deref = RefCount.deref;
pub fn getTarget(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, clean_target: bool) jsc.JSValue {
const thisValue = this.thisValue.get();
if (thisValue == .zero) {
return .zero;
}
const thisValue = this.thisValue.tryGet() orelse return .zero;
const target = js.targetGetCached(thisValue) orelse return .zero;
if (clean_target) {
js.targetSetCached(thisValue, globalObject, .zero);
@@ -52,7 +52,7 @@ pub const Status = enum(u8) {
};
pub fn hasPendingActivity(this: *@This()) bool {
return this.ref_count.load(.monotonic) > 1;
return this.ref_count.getCount() > 1;
}
pub fn deinit(this: *@This()) void {
@@ -75,24 +75,14 @@ pub fn finalize(this: *@This()) void {
this.deref();
}
pub fn deref(this: *@This()) void {
const ref_count = this.ref_count.fetchSub(1, .monotonic);
if (ref_count == 1) {
this.deinit();
}
}
pub fn ref(this: *@This()) void {
bun.assert(this.ref_count.fetchAdd(1, .monotonic) > 0);
}
pub fn onWriteFail(
this: *@This(),
err: AnyPostgresError,
globalObject: *jsc.JSGlobalObject,
queries_array: JSValue,
) void {
this.ref();
defer this.deref();
this.status = .fail;
const thisValue = this.thisValue.get();
defer this.thisValue.deinit();
@@ -111,10 +101,9 @@ pub fn onWriteFail(
});
}
pub fn onJSError(this: *@This(), err: jsc.JSValue, globalObject: *jsc.JSGlobalObject) void {
this.status = .fail;
this.ref();
defer this.deref();
this.status = .fail;
const thisValue = this.thisValue.get();
defer this.thisValue.deinit();
const targetValue = this.getTarget(globalObject, true);
@@ -268,7 +257,8 @@ pub fn doDone(this: *@This(), globalObject: *jsc.JSGlobalObject, _: *jsc.CallFra
}
pub fn setPendingValue(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!JSValue {
const result = callframe.argument(0);
js.pendingValueSetCached(this.thisValue.get(), globalObject, result);
const thisValue = this.thisValue.tryGet() orelse return .js_undefined;
js.pendingValueSetCached(thisValue, globalObject, result);
return .js_undefined;
}
pub fn setMode(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callframe: *jsc.CallFrame) bun.JSError!JSValue {
@@ -303,13 +293,30 @@ pub fn doRun(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callfra
var query_str = this.query.toUTF8(bun.default_allocator);
defer query_str.deinit();
var writer = connection.writer();
// We need a strong reference to the query so that it doesn't get GC'd
this.ref();
if (this.flags.simple) {
debug("executeQuery", .{});
const stmt = bun.default_allocator.create(PostgresSQLStatement) catch {
this.deref();
return globalObject.throwOutOfMemory();
};
// Query is simple and it's the only owner of the statement
stmt.* = .{
.signature = Signature.empty(),
.status = .parsing,
};
this.statement = stmt;
const can_execute = !connection.hasQueryRunning();
if (can_execute) {
PostgresRequest.executeQuery(query_str.slice(), PostgresSQLConnection.Writer, writer) catch |err| {
// fail to run do cleanup
this.statement = null;
bun.default_allocator.destroy(stmt);
this.deref();
if (!globalObject.hasException())
return globalObject.throwValue(postgresErrorToJS(globalObject, "failed to execute query", err));
return error.JSError;
@@ -320,21 +327,16 @@ pub fn doRun(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callfra
} else {
this.status = .pending;
}
const stmt = bun.default_allocator.create(PostgresSQLStatement) catch {
connection.requests.writeItem(this) catch {
// fail to run do cleanup
this.statement = null;
bun.default_allocator.destroy(stmt);
this.deref();
return globalObject.throwOutOfMemory();
};
// Query is simple and it's the only owner of the statement
stmt.* = .{
.signature = Signature.empty(),
.ref_count = 1,
.status = .parsing,
};
this.statement = stmt;
// We need a strong reference to the query so that it doesn't get GC'd
connection.requests.writeItem(this) catch return globalObject.throwOutOfMemory();
this.ref();
this.thisValue.upgrade(globalObject);
this.thisValue.upgrade(globalObject);
js.targetSetCached(this_value, globalObject, query);
if (this.status == .running) {
connection.flushDataAndResetTimeout();
@@ -347,6 +349,7 @@ pub fn doRun(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callfra
const columns_value: JSValue = js.columnsGetCached(this_value) orelse .js_undefined;
var signature = Signature.generate(globalObject, query_str.slice(), binding_value, columns_value, connection.prepared_statement_id, connection.flags.use_unnamed_prepared_statements) catch |err| {
this.deref();
if (!globalObject.hasException())
return globalObject.throwError(err, "failed to generate signature");
return error.JSError;
@@ -363,12 +366,16 @@ pub fn doRun(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callfra
};
connection_entry_value = entry.value_ptr;
if (entry.found_existing) {
this.statement = connection_entry_value.?.*;
this.statement.?.ref();
const stmt = connection_entry_value.?.*;
this.statement = stmt;
stmt.ref();
signature.deinit();
switch (this.statement.?.status) {
switch (stmt.status) {
.failed => {
this.statement = null;
stmt.deref();
this.deref();
// If the statement failed, we need to throw the error
return globalObject.throwValue(this.statement.?.error_response.?.toJS(globalObject));
},
@@ -379,6 +386,11 @@ pub fn doRun(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callfra
// bindAndExecute will bind + execute, it will change to running after binding is complete
PostgresRequest.bindAndExecute(globalObject, this.statement.?, binding_value, columns_value, PostgresSQLConnection.Writer, writer) catch |err| {
// fail to run do cleanup
this.statement = null;
stmt.deref();
this.deref();
if (!globalObject.hasException())
return globalObject.throwValue(postgresErrorToJS(globalObject, "failed to bind and execute query", err));
return error.JSError;
@@ -406,6 +418,11 @@ pub fn doRun(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callfra
// prepareAndQueryWithSignature will write + bind + execute, it will change to running after binding is complete
PostgresRequest.prepareAndQueryWithSignature(globalObject, query_str.slice(), binding_value, PostgresSQLConnection.Writer, writer, &signature) catch |err| {
signature.deinit();
if (this.statement) |stmt| {
this.statement = null;
stmt.deref();
}
this.deref();
if (!globalObject.hasException())
return globalObject.throwValue(postgresErrorToJS(globalObject, "failed to prepare and query", err));
return error.JSError;
@@ -419,6 +436,11 @@ pub fn doRun(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callfra
PostgresRequest.writeQuery(query_str.slice(), signature.prepared_statement_name, signature.fields, PostgresSQLConnection.Writer, writer) catch |err| {
signature.deinit();
if (this.statement) |stmt| {
this.statement = null;
stmt.deref();
}
this.deref();
if (!globalObject.hasException())
return globalObject.throwValue(postgresErrorToJS(globalObject, "failed to write query", err));
return error.JSError;
@@ -436,24 +458,31 @@ pub fn doRun(this: *PostgresSQLQuery, globalObject: *jsc.JSGlobalObject, callfra
}
{
const stmt = bun.default_allocator.create(PostgresSQLStatement) catch {
this.deref();
return globalObject.throwOutOfMemory();
};
// we only have connection_entry_value if we are using named prepared statements
if (connection_entry_value) |entry_value| {
connection.prepared_statement_id += 1;
stmt.* = .{ .signature = signature, .ref_count = 2, .status = if (can_execute) .parsing else .pending };
stmt.* = .{
.signature = signature,
.ref_count = .initExactRefs(2),
.status = if (can_execute) .parsing else .pending,
};
this.statement = stmt;
entry_value.* = stmt;
} else {
stmt.* = .{ .signature = signature, .ref_count = 1, .status = if (can_execute) .parsing else .pending };
stmt.* = .{
.signature = signature,
.status = if (can_execute) .parsing else .pending,
};
this.statement = stmt;
}
}
}
// We need a strong reference to the query so that it doesn't get GC'd
connection.requests.writeItem(this) catch return globalObject.throwOutOfMemory();
this.ref();
this.thisValue.upgrade(globalObject);
js.targetSetCached(this_value, globalObject, query);

View File

@@ -1,7 +1,7 @@
const PostgresSQLStatement = @This();
const RefCount = bun.ptr.RefCount(@This(), "ref_count", deinit, .{});
cached_structure: PostgresCachedStructure = .{},
ref_count: u32 = 1,
ref_count: RefCount = RefCount.init(),
fields: []protocol.FieldDescription = &[_]protocol.FieldDescription{},
parameters: []const int4 = &[_]int4{},
signature: Signature,
@@ -9,6 +9,8 @@ status: Status = Status.pending,
error_response: ?Error = null,
needs_duplicate_check: bool = true,
fields_flags: DataCell.Flags = .{},
pub const ref = RefCount.ref;
pub const deref = RefCount.deref;
pub const Error = union(enum) {
protocol: protocol.ErrorResponse,
@@ -38,19 +40,6 @@ pub const Status = enum {
return this == .parsing;
}
};
pub fn ref(this: *@This()) void {
bun.assert(this.ref_count > 0);
this.ref_count += 1;
}
pub fn deref(this: *@This()) void {
const ref_count = this.ref_count;
this.ref_count -= 1;
if (ref_count == 1) {
this.deinit();
}
}
pub fn checkForDuplicateFields(this: *PostgresSQLStatement) void {
if (!this.needs_duplicate_check) return;
@@ -100,7 +89,7 @@ pub fn checkForDuplicateFields(this: *PostgresSQLStatement) void {
pub fn deinit(this: *PostgresSQLStatement) void {
debug("PostgresSQLStatement deinit", .{});
bun.assert(this.ref_count == 0);
this.ref_count.assertNoRefs();
for (this.fields) |*field| {
field.deinit();

View File

@@ -57,10 +57,14 @@ pub fn NewReaderWrap(
pub fn int(this: @This(), comptime Int: type) !Int {
var data = try this.read(@sizeOf((Int)));
defer data.deinit();
if (comptime Int == u8) {
return @as(Int, data.slice()[0]);
const slice = data.slice();
if (slice.len < @sizeOf(Int)) {
return error.ShortRead;
}
return @byteSwap(@as(Int, @bitCast(data.slice()[0..@sizeOf(Int)].*)));
if (comptime Int == u8) {
return @as(Int, slice[0]);
}
return @byteSwap(@as(Int, @bitCast(slice[0..@sizeOf(Int)].*)));
}
pub fn peekInt(this: @This(), comptime Int: type) ?Int {

View File

@@ -41,6 +41,23 @@ describe("bundler", () => {
"zlib": "polyfill",
};
itBundled("browser/NodeBuffer#21522", {
files: {
"/entry.js": /* js */ `
import { Buffer } from "node:buffer";
const x = Buffer.alloc(5);
x.write("68656c6c6f", "hex");
console.log(x);
`,
},
target: "browser",
run: {
stdout: "<Buffer 68 65 6c 6c 6f>",
},
onAfterBundle(api) {
api.expectFile("out.js").not.toInclude("import ");
},
});
itBundled("browser/NodeBuffer#12272", {
files: {
"/entry.js": /* js */ `

View File

@@ -34,3 +34,19 @@ const query3 = db.prepare<
>("select name, dob from users where id = $id");
const allResults3 = query3.all({ $id: "asdf" });
expectType<Array<{ name: string; dob: number }>>(allResults3);
db.exec("CREATE TABLE cats (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE, age INTEGER)");
const insert = db.prepare("INSERT INTO cats (name, age) VALUES ($name, $age)");
const insertManyCats = db.transaction((cats: Array<{ $name: string; $age: number }>) => {
for (const cat of cats) insert.run(cat);
});
insertManyCats([
{
$name: "Joey",
$age: 2,
},
{ $name: "Sally", $age: 4 },
{ $name: "Junior", $age: 1 },
// @ts-expect-error - Should fail
{ fail: true },
]);

View File

@@ -0,0 +1,155 @@
-- Insert 50 Users
INSERT INTO users (id, name, email, identifier, role, phone, bio, skills, privacy, linkedin_url, github_url, facebook_url, twitter_url, picture) VALUES
('alice01', 'Alice Anderson', 'alice01@example.com', 'alice01', 'CUSTOMER', '+1-555-111-0001', '{"about": "Data Scientist", "location": "Los Angeles"}', '["Figma", "Sketch", "UI/UX"]', 'PRIVATE', NULL, 'https://github.com/alice01', NULL, NULL, '{"url": "https://pics.example.com/alice01.jpg"}'),
('bob02', 'Bob Moore', 'bob02@example.com', 'bob02', 'CUSTOMER', '+1-555-111-0002', '{"about": "Cloud Engineer", "location": "Seattle"}', '["Figma", "Sketch", "UI/UX"]', 'PRIVATE', 'https://linkedin.com/in/bob02', 'https://github.com/bob02', NULL, 'https://twitter.com/bob02', '{"url": "https://pics.example.com/bob02.jpg"}'),
('charlie03', 'Charlie Adams', 'charlie03@example.com', 'charlie03', 'CUSTOMER', '+1-555-111-0003', '{"about": "Digital Marketer", "location": "Boston"}', '["Java", "Spring Boot", "Microservices"]', 'PUBLIC', NULL, 'https://github.com/charlie03', NULL, 'https://twitter.com/charlie03', '{"url": "https://pics.example.com/charlie03.jpg"}'),
('diana04', 'Diana Johnson', 'diana04@example.com', 'diana04', 'CUSTOMER', '+1-555-111-0004', '{"about": "Digital Marketer", "location": "New York"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', NULL, 'https://github.com/diana04', NULL, 'https://twitter.com/diana04', '{"url": "https://pics.example.com/diana04.jpg"}'),
('ethan05', 'Ethan Brown', 'ethan05@example.com', 'ethan05', 'CUSTOMER', '+1-555-111-0005', '{"about": "Digital Marketer", "location": "Seattle"}', '["Flutter", "Dart", "Firebase"]', 'PRIVATE', 'https://linkedin.com/in/ethan05', 'https://github.com/ethan05', NULL, 'https://twitter.com/ethan05', '{"url": "https://pics.example.com/ethan05.jpg"}'),
('fiona06', 'Fiona Adams', 'fiona06@example.com', 'fiona06', 'CUSTOMER', '+1-555-111-0006', '{"about": "Digital Marketer", "location": "Los Angeles"}', '["Kubernetes", "Docker", "CI/CD"]', 'PUBLIC', 'https://linkedin.com/in/fiona06', 'https://github.com/fiona06', NULL, 'https://twitter.com/fiona06', '{"url": "https://pics.example.com/fiona06.jpg"}'),
('george07', 'George Wilson', 'george07@example.com', 'george07', 'CUSTOMER', '+1-555-111-0007', '{"about": "Data Scientist", "location": "Los Angeles"}', '["Python", "Pandas", "Machine Learning"]', 'PRIVATE', 'https://linkedin.com/in/george07', NULL, NULL, 'https://twitter.com/george07', '{"url": "https://pics.example.com/george07.jpg"}'),
('hannah08', 'Hannah Moore', 'hannah08@example.com', 'hannah08', 'CUSTOMER', '+1-555-111-0008', '{"about": "UX Designer", "location": "Chicago"}', '["Deep Learning", "NLP", "PyTorch"]', 'PUBLIC', 'https://linkedin.com/in/hannah08', 'https://github.com/hannah08', NULL, NULL, '{"url": "https://pics.example.com/hannah08.jpg"}'),
('ian09', 'Ian Adams', 'ian09@example.com', 'ian09', 'CUSTOMER', '+1-555-111-0009', '{"about": "Cloud Engineer", "location": "Boston"}', '["Deep Learning", "NLP", "PyTorch"]', 'PRIVATE', 'https://linkedin.com/in/ian09', 'https://github.com/ian09', NULL, 'https://twitter.com/ian09', '{"url": "https://pics.example.com/ian09.jpg"}'),
('julia10', 'Julia Thomas', 'julia10@example.com', 'julia10', 'CUSTOMER', '+1-555-111-0010', '{"about": "Cloud Engineer", "location": "New York"}', '["Python", "Pandas", "Machine Learning"]', 'PUBLIC', 'https://linkedin.com/in/julia10', NULL, NULL, 'https://twitter.com/julia10', '{"url": "https://pics.example.com/julia10.jpg"}'),
('kevin11', 'Kevin Wilson', 'kevin11@example.com', 'kevin11', 'CUSTOMER', '+1-555-111-0011', '{"about": "Full stack developer", "location": "New York"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', NULL, NULL, NULL, 'https://twitter.com/kevin11', '{"url": "https://pics.example.com/kevin11.jpg"}'),
('laura12', 'Laura Lee', 'laura12@example.com', 'laura12', 'CUSTOMER', '+1-555-111-0012', '{"about": "Full stack developer", "location": "Chicago"}', '["Kubernetes", "Docker", "CI/CD"]', 'PUBLIC', 'https://linkedin.com/in/laura12', NULL, NULL, NULL, '{"url": "https://pics.example.com/laura12.jpg"}'),
('mike13', 'Mike Wilson', 'mike13@example.com', 'mike13', 'CUSTOMER', '+1-555-111-0013', '{"about": "Full stack developer", "location": "San Francisco"}', '["Deep Learning", "NLP", "PyTorch"]', 'PUBLIC', 'https://linkedin.com/in/mike13', 'https://github.com/mike13', NULL, NULL, '{"url": "https://pics.example.com/mike13.jpg"}'),
('nina14', 'Nina Wilson', 'nina14@example.com', 'nina14', 'CUSTOMER', '+1-555-111-0014', '{"about": "UX Designer", "location": "Seattle"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', 'https://linkedin.com/in/nina14', 'https://github.com/nina14', NULL, NULL, '{"url": "https://pics.example.com/nina14.jpg"}'),
('oscar15', 'Oscar Johnson', 'oscar15@example.com', 'oscar15', 'CUSTOMER', '+1-555-111-0015', '{"about": "Cloud Engineer", "location": "Boston"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', NULL, 'https://github.com/oscar15', NULL, NULL, '{"url": "https://pics.example.com/oscar15.jpg"}'),
('paula16', 'Paula Taylor', 'paula16@example.com', 'paula16', 'CUSTOMER', '+1-555-111-0016', '{"about": "Cloud Engineer", "location": "New York"}', '["Deep Learning", "NLP", "PyTorch"]', 'PRIVATE', 'https://linkedin.com/in/paula16', NULL, NULL, 'https://twitter.com/paula16', '{"url": "https://pics.example.com/paula16.jpg"}'),
('quinn17', 'Quinn Thomas', 'quinn17@example.com', 'quinn17', 'CUSTOMER', '+1-555-111-0017', '{"about": "Full stack developer", "location": "New York"}', '["Python", "Pandas", "Machine Learning"]', 'PUBLIC', 'https://linkedin.com/in/quinn17', NULL, NULL, NULL, '{"url": "https://pics.example.com/quinn17.jpg"}'),
('ryan18', 'Ryan Wilson', 'ryan18@example.com', 'ryan18', 'CUSTOMER', '+1-555-111-0018', '{"about": "UX Designer", "location": "Los Angeles"}', '["Deep Learning", "NLP", "PyTorch"]', 'PUBLIC', 'https://linkedin.com/in/ryan18', 'https://github.com/ryan18', NULL, NULL, '{"url": "https://pics.example.com/ryan18.jpg"}'),
('sophia19', 'Sophia Lee', 'sophia19@example.com', 'sophia19', 'CUSTOMER', '+1-555-111-0019', '{"about": "Mobile App Developer", "location": "San Francisco"}', '["Flutter", "Dart", "Firebase"]', 'PUBLIC', 'https://linkedin.com/in/sophia19', 'https://github.com/sophia19', NULL, NULL, '{"url": "https://pics.example.com/sophia19.jpg"}'),
('tom20', 'Tom Thomas', 'tom20@example.com', 'tom20', 'CUSTOMER', '+1-555-111-0020', '{"about": "Digital Marketer", "location": "Chicago"}', '["AWS", "Terraform", "DevOps"]', 'PRIVATE', 'https://linkedin.com/in/tom20', 'https://github.com/tom20', NULL, 'https://twitter.com/tom20', '{"url": "https://pics.example.com/tom20.jpg"}'),
('uma21', 'Uma Johnson', 'uma21@example.com', 'uma21', 'CUSTOMER', '+1-555-111-0021', '{"about": "Data Scientist", "location": "Los Angeles"}', '["Java", "Spring Boot", "Microservices"]', 'PRIVATE', 'https://linkedin.com/in/uma21', NULL, NULL, NULL, '{"url": "https://pics.example.com/uma21.jpg"}'),
('victor22', 'Victor Brown', 'victor22@example.com', 'victor22', 'CUSTOMER', '+1-555-111-0022', '{"about": "Cloud Engineer", "location": "New York"}', '["Deep Learning", "NLP", "PyTorch"]', 'PUBLIC', 'https://linkedin.com/in/victor22', 'https://github.com/victor22', NULL, 'https://twitter.com/victor22', '{"url": "https://pics.example.com/victor22.jpg"}'),
('wendy23', 'Wendy Anderson', 'wendy23@example.com', 'wendy23', 'CUSTOMER', '+1-555-111-0023', '{"about": "Mobile App Developer", "location": "Chicago"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', 'https://linkedin.com/in/wendy23', 'https://github.com/wendy23', NULL, 'https://twitter.com/wendy23', '{"url": "https://pics.example.com/wendy23.jpg"}'),
('xavier24', 'Xavier Taylor', 'xavier24@example.com', 'xavier24', 'CUSTOMER', '+1-555-111-0024', '{"about": "Digital Marketer", "location": "Boston"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', 'https://linkedin.com/in/xavier24', 'https://github.com/xavier24', NULL, 'https://twitter.com/xavier24', '{"url": "https://pics.example.com/xavier24.jpg"}'),
('yara25', 'Yara Brown', 'yara25@example.com', 'yara25', 'CUSTOMER', '+1-555-111-0025', '{"about": "UX Designer", "location": "New York"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', NULL, NULL, NULL, 'https://twitter.com/yara25', '{"url": "https://pics.example.com/yara25.jpg"}'),
('zane26', 'Zane Taylor', 'zane26@example.com', 'zane26', 'CUSTOMER', '+1-555-111-0026', '{"about": "Cloud Engineer", "location": "Seattle"}', '["JavaScript", "React", "Node.js"]', 'PUBLIC', 'https://linkedin.com/in/zane26', 'https://github.com/zane26', NULL, 'https://twitter.com/zane26', '{"url": "https://pics.example.com/zane26.jpg"}'),
('amber27', 'Amber Wilson', 'amber27@example.com', 'amber27', 'CUSTOMER', '+1-555-111-0027', '{"about": "Digital Marketer", "location": "New York"}', '["Kubernetes", "Docker", "CI/CD"]', 'PUBLIC', NULL, 'https://github.com/amber27', NULL, 'https://twitter.com/amber27', '{"url": "https://pics.example.com/amber27.jpg"}'),
('brian28', 'Brian Wilson', 'brian28@example.com', 'brian28', 'CUSTOMER', '+1-555-111-0028', '{"about": "Digital Marketer", "location": "Chicago"}', '["Figma", "Sketch", "UI/UX"]', 'PRIVATE', 'https://linkedin.com/in/brian28', NULL, NULL, 'https://twitter.com/brian28', '{"url": "https://pics.example.com/brian28.jpg"}'),
('carmen29', 'Carmen Moore', 'carmen29@example.com', 'carmen29', 'CUSTOMER', '+1-555-111-0029', '{"about": "Digital Marketer", "location": "Boston"}', '["Kubernetes", "Docker", "CI/CD"]', 'PRIVATE', 'https://linkedin.com/in/carmen29', NULL, NULL, 'https://twitter.com/carmen29', '{"url": "https://pics.example.com/carmen29.jpg"}'),
('daniel30', 'Daniel Smith', 'daniel30@example.com', 'daniel30', 'CUSTOMER', '+1-555-111-0030', '{"about": "Full stack developer", "location": "New York"}', '["AWS", "Terraform", "DevOps"]', 'PUBLIC', 'https://linkedin.com/in/daniel30', 'https://github.com/daniel30', NULL, NULL, '{"url": "https://pics.example.com/daniel30.jpg"}'),
('elena31', 'Elena Lee', 'elena31@example.com', 'elena31', 'CUSTOMER', '+1-555-111-0031', '{"about": "Mobile App Developer", "location": "San Francisco"}', '["SEO", "Content Marketing"]', 'PRIVATE', NULL, 'https://github.com/elena31', NULL, NULL, '{"url": "https://pics.example.com/elena31.jpg"}'),
('frank32', 'Frank Johnson', 'frank32@example.com', 'frank32', 'CUSTOMER', '+1-555-111-0032', '{"about": "Digital Marketer", "location": "San Francisco"}', '["AWS", "Terraform", "DevOps"]', 'PUBLIC', 'https://linkedin.com/in/frank32', 'https://github.com/frank32', NULL, 'https://twitter.com/frank32', '{"url": "https://pics.example.com/frank32.jpg"}'),
('grace33', 'Grace Adams', 'grace33@example.com', 'grace33', 'CUSTOMER', '+1-555-111-0033', '{"about": "Mobile App Developer", "location": "Seattle"}', '["AWS", "Terraform", "DevOps"]', 'PUBLIC', NULL, 'https://github.com/grace33', NULL, 'https://twitter.com/grace33', '{"url": "https://pics.example.com/grace33.jpg"}'),
('henry34', 'Henry Adams', 'henry34@example.com', 'henry34', 'CUSTOMER', '+1-555-111-0034', '{"about": "AI Researcher", "location": "San Francisco"}', '["JavaScript", "React", "Node.js"]', 'PUBLIC', 'https://linkedin.com/in/henry34', 'https://github.com/henry34', NULL, 'https://twitter.com/henry34', '{"url": "https://pics.example.com/henry34.jpg"}'),
('isla35', 'Isla Adams', 'isla35@example.com', 'isla35', 'CUSTOMER', '+1-555-111-0035', '{"about": "Cloud Engineer", "location": "Los Angeles"}', '["Flutter", "Dart", "Firebase"]', 'PRIVATE', NULL, 'https://github.com/isla35', NULL, 'https://twitter.com/isla35', '{"url": "https://pics.example.com/isla35.jpg"}'),
('jack36', 'Jack Johnson', 'jack36@example.com', 'jack36', 'CUSTOMER', '+1-555-111-0036', '{"about": "UX Designer", "location": "Seattle"}', '["Kubernetes", "Docker", "CI/CD"]', 'PRIVATE', 'https://linkedin.com/in/jack36', NULL, NULL, NULL, '{"url": "https://pics.example.com/jack36.jpg"}'),
('kara37', 'Kara Moore', 'kara37@example.com', 'kara37', 'CUSTOMER', '+1-555-111-0037', '{"about": "Data Scientist", "location": "Chicago"}', '["JavaScript", "React", "Node.js"]', 'PRIVATE', 'https://linkedin.com/in/kara37', 'https://github.com/kara37', NULL, 'https://twitter.com/kara37', '{"url": "https://pics.example.com/kara37.jpg"}'),
('liam38', 'Liam Adams', 'liam38@example.com', 'liam38', 'CUSTOMER', '+1-555-111-0038', '{"about": "Cloud Engineer", "location": "Seattle"}', '["Python", "Pandas", "Machine Learning"]', 'PUBLIC', 'https://linkedin.com/in/liam38', 'https://github.com/liam38', NULL, 'https://twitter.com/liam38', '{"url": "https://pics.example.com/liam38.jpg"}'),
('maya39', 'Maya Brown', 'maya39@example.com', 'maya39', 'CUSTOMER', '+1-555-111-0039', '{"about": "UX Designer", "location": "Los Angeles"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', NULL, 'https://github.com/maya39', NULL, 'https://twitter.com/maya39', '{"url": "https://pics.example.com/maya39.jpg"}'),
('noah40', 'Noah Johnson', 'noah40@example.com', 'noah40', 'CUSTOMER', '+1-555-111-0040', '{"about": "Cloud Engineer", "location": "Chicago"}', '["Kubernetes", "Docker", "CI/CD"]', 'PRIVATE', 'https://linkedin.com/in/noah40', 'https://github.com/noah40', NULL, 'https://twitter.com/noah40', '{"url": "https://pics.example.com/noah40.jpg"}'),
('olivia41', 'Olivia Thomas', 'olivia41@example.com', 'olivia41', 'CUSTOMER', '+1-555-111-0041', '{"about": "AI Researcher", "location": "Boston"}', '["Java", "Spring Boot", "Microservices"]', 'PRIVATE', 'https://linkedin.com/in/olivia41', 'https://github.com/olivia41', NULL, 'https://twitter.com/olivia41', '{"url": "https://pics.example.com/olivia41.jpg"}'),
('peter42', 'Peter Adams', 'peter42@example.com', 'peter42', 'CUSTOMER', '+1-555-111-0042', '{"about": "Cloud Engineer", "location": "New York"}', '["JavaScript", "React", "Node.js"]', 'PRIVATE', 'https://linkedin.com/in/peter42', 'https://github.com/peter42', NULL, NULL, '{"url": "https://pics.example.com/peter42.jpg"}'),
('queen43', 'Queen Brown', 'queen43@example.com', 'queen43', 'CUSTOMER', '+1-555-111-0043', '{"about": "UX Designer", "location": "Seattle"}', '["SEO", "Content Marketing"]', 'PRIVATE', NULL, NULL, NULL, 'https://twitter.com/queen43', '{"url": "https://pics.example.com/queen43.jpg"}'),
('rita44', 'Rita Moore', 'rita44@example.com', 'rita44', 'CUSTOMER', '+1-555-111-0044', '{"about": "Mobile App Developer", "location": "Los Angeles"}', '["Deep Learning", "NLP", "PyTorch"]', 'PRIVATE', NULL, 'https://github.com/rita44', NULL, 'https://twitter.com/rita44', '{"url": "https://pics.example.com/rita44.jpg"}'),
('samuel45', 'Samuel Moore', 'samuel45@example.com', 'samuel45', 'CUSTOMER', '+1-555-111-0045', '{"about": "Full stack developer", "location": "Boston"}', '["Go", "gRPC", "PostgreSQL"]', 'PRIVATE', 'https://linkedin.com/in/samuel45', NULL, NULL, 'https://twitter.com/samuel45', '{"url": "https://pics.example.com/samuel45.jpg"}'),
('tina46', 'Tina Wilson', 'tina46@example.com', 'tina46', 'CUSTOMER', '+1-555-111-0046', '{"about": "Mobile App Developer", "location": "Los Angeles"}', '["Python", "Pandas", "Machine Learning"]', 'PUBLIC', 'https://linkedin.com/in/tina46', 'https://github.com/tina46', NULL, 'https://twitter.com/tina46', '{"url": "https://pics.example.com/tina46.jpg"}'),
('ursula47', 'Ursula Wilson', 'ursula47@example.com', 'ursula47', 'CUSTOMER', '+1-555-111-0047', '{"about": "Digital Marketer", "location": "Seattle"}', '["Flutter", "Dart", "Firebase"]', 'PUBLIC', NULL, 'https://github.com/ursula47', NULL, 'https://twitter.com/ursula47', '{"url": "https://pics.example.com/ursula47.jpg"}'),
('vera48', 'Vera Anderson', 'vera48@example.com', 'vera48', 'CUSTOMER', '+1-555-111-0048', '{"about": "Cloud Engineer", "location": "San Francisco"}', '["AWS", "Terraform", "DevOps"]', 'PUBLIC', 'https://linkedin.com/in/vera48', 'https://github.com/vera48', NULL, 'https://twitter.com/vera48', '{"url": "https://pics.example.com/vera48.jpg"}'),
('william49', 'William Lee', 'william49@example.com', 'william49', 'CUSTOMER', '+1-555-111-0049', '{"about": "AI Researcher", "location": "Los Angeles"}', '["Go", "gRPC", "PostgreSQL"]', 'PUBLIC', 'https://linkedin.com/in/william49', 'https://github.com/william49', NULL, NULL, '{"url": "https://pics.example.com/william49.jpg"}'),
('zoe50', 'Zoe Thomas', 'zoe50@example.com', 'zoe50', 'CUSTOMER', '+1-555-111-0050', '{"about": "Digital Marketer", "location": "Los Angeles"}', '["Flutter", "Dart", "Firebase"]', 'PRIVATE', NULL, 'https://github.com/zoe50', NULL, NULL, '{"url": "https://pics.example.com/zoe50.jpg"}');
-- Insert 100+ Posts (2 per user)
INSERT INTO posts (user_id, title, content, tags, type, attachments) VALUES
('alice01', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["React", "JavaScript"]', 'draft', '[{"url": "https://files.example.com/intro-to-machine-learning.pdf"}]'),
('alice01', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["DevOps", "Cloud"]', 'published', '[{"url": "https://files.example.com/advanced-node.js-patterns.pdf"}]'),
('bob02', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["NLP", "AI"]', 'published', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'),
('bob02', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["Python", "Pandas"]', 'published', '[{"url": "https://files.example.com/intro-to-machine-learning.pdf"}]'),
('charlie03', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["Python", "Pandas"]', 'published', '[{"url": "https://files.example.com/data-cleaning-with-pandas.pdf"}]'),
('charlie03', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["DevOps", "Cloud"]', 'draft', '[]'),
('diana04', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["DevOps", "Cloud"]', 'draft', '[]'),
('diana04', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["Python", "Pandas"]', 'draft', '[]'),
('ethan05', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["Deep Learning", "Python"]', 'draft', '[]'),
('ethan05', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["Backend", "Node.js"]', 'published', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'),
('fiona06', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["ML", "AI"]', 'draft', '[]'),
('fiona06', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["React", "JavaScript"]', 'draft', '[]'),
('george07', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["ML", "AI"]', 'published', '[]'),
('george07', 'Building Mobile Apps with Flutter', '{"text": "This is a detailed guide about Building Mobile Apps with Flutter."}', '["ML", "AI"]', 'published', '[{"url": "https://files.example.com/building-mobile-apps-with-flutter.pdf"}]'),
('hannah08', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["ML", "AI"]', 'published', '[{"url": "https://files.example.com/ui-design-best-practices.pdf"}]'),
('hannah08', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["Kubernetes", "Docker"]', 'published', '[]'),
('ian09', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["UI", "Design"]', 'draft', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'),
('ian09', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["ML", "AI"]', 'published', '[{"url": "https://files.example.com/data-cleaning-with-pandas.pdf"}]'),
('julia10', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["ML", "AI"]', 'draft', '[]'),
('julia10', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["Deep Learning", "Python"]', 'published', '[{"url": "https://files.example.com/understanding-react-hooks.pdf"}]'),
('kevin11', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["NLP", "AI"]', 'published', '[]'),
('kevin11', 'Building Mobile Apps with Flutter', '{"text": "This is a detailed guide about Building Mobile Apps with Flutter."}', '["Python", "Pandas"]', 'published', '[]'),
('laura12', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["DevOps", "Cloud"]', 'draft', '[]'),
('laura12', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["NLP", "AI"]', 'draft', '[{"url": "https://files.example.com/intro-to-machine-learning.pdf"}]'),
('mike13', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["ML", "AI"]', 'draft', '[]'),
('mike13', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["Kubernetes", "Docker"]', 'draft', '[]'),
('nina14', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["UI", "Design"]', 'draft', '[]'),
('nina14', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["UI", "Design"]', 'draft', '[{"url": "https://files.example.com/ui-design-best-practices.pdf"}]'),
('oscar15', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["NLP", "AI"]', 'published', '[{"url": "https://files.example.com/understanding-react-hooks.pdf"}]'),
('oscar15', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["NLP", "AI"]', 'published', '[]'),
('paula16', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["Python", "Pandas"]', 'draft', '[]'),
('paula16', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["Kubernetes", "Docker"]', 'draft', '[{"url": "https://files.example.com/kubernetes-basics.pdf"}]'),
('quinn17', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["ML", "AI"]', 'published', '[]'),
('quinn17', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["Deep Learning", "Python"]', 'published', '[{"url": "https://files.example.com/getting-started-with-nlp.pdf"}]'),
('ryan18', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["React", "JavaScript"]', 'published', '[]'),
('ryan18', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["Python", "Pandas"]', 'draft', '[]'),
('sophia19', 'Deep Learning for Beginners', '{"text": "This is a detailed guide about Deep Learning for Beginners."}', '["NLP", "AI"]', 'published', '[]'),
('sophia19', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["Mobile", "Flutter"]', 'draft', '[]'),
('tom20', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["Python", "Pandas"]', 'draft', '[]'),
('tom20', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["NLP", "AI"]', 'published', '[]'),
('uma21', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["Backend", "Node.js"]', 'draft', '[{"url": "https://files.example.com/kubernetes-basics.pdf"}]'),
('uma21', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["Mobile", "Flutter"]', 'draft', '[]'),
('victor22', 'Deep Learning for Beginners', '{"text": "This is a detailed guide about Deep Learning for Beginners."}', '["Python", "Pandas"]', 'draft', '[]'),
('victor22', 'Building Mobile Apps with Flutter', '{"text": "This is a detailed guide about Building Mobile Apps with Flutter."}', '["UI", "Design"]', 'published', '[{"url": "https://files.example.com/building-mobile-apps-with-flutter.pdf"}]'),
('wendy23', 'Deep Learning for Beginners', '{"text": "This is a detailed guide about Deep Learning for Beginners."}', '["NLP", "AI"]', 'draft', '[{"url": "https://files.example.com/deep-learning-for-beginners.pdf"}]'),
('wendy23', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["Backend", "Node.js"]', 'draft', '[{"url": "https://files.example.com/advanced-node.js-patterns.pdf"}]'),
('xavier24', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["UI", "Design"]', 'published', '[]'),
('xavier24', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["Python", "Pandas"]', 'draft', '[]'),
('yara25', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["Deep Learning", "Python"]', 'published', '[]'),
('yara25', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["NLP", "AI"]', 'published', '[]'),
('zane26', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["React", "JavaScript"]', 'draft', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'),
('zane26', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["NLP", "AI"]', 'draft', '[]'),
('amber27', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["DevOps", "Cloud"]', 'draft', '[{"url": "https://files.example.com/kubernetes-basics.pdf"}]'),
('amber27', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["Mobile", "Flutter"]', 'draft', '[]'),
('brian28', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["Backend", "Node.js"]', 'draft', '[{"url": "https://files.example.com/ui-design-best-practices.pdf"}]'),
('brian28', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["React", "JavaScript"]', 'draft', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'),
('carmen29', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["DevOps", "Cloud"]', 'published', '[{"url": "https://files.example.com/intro-to-machine-learning.pdf"}]'),
('carmen29', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["NLP", "AI"]', 'published', '[]'),
('daniel30', 'Building Mobile Apps with Flutter', '{"text": "This is a detailed guide about Building Mobile Apps with Flutter."}', '["ML", "AI"]', 'draft', '[{"url": "https://files.example.com/building-mobile-apps-with-flutter.pdf"}]'),
('daniel30', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["NLP", "AI"]', 'draft', '[]'),
('elena31', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["React", "JavaScript"]', 'draft', '[{"url": "https://files.example.com/getting-started-with-nlp.pdf"}]'),
('elena31', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["Python", "Pandas"]', 'draft', '[{"url": "https://files.example.com/advanced-node.js-patterns.pdf"}]'),
('frank32', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["ML", "AI"]', 'draft', '[{"url": "https://files.example.com/kubernetes-basics.pdf"}]'),
('frank32', 'Building Mobile Apps with Flutter', '{"text": "This is a detailed guide about Building Mobile Apps with Flutter."}', '["UI", "Design"]', 'draft', '[{"url": "https://files.example.com/building-mobile-apps-with-flutter.pdf"}]'),
('grace33', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["ML", "AI"]', 'draft', '[{"url": "https://files.example.com/getting-started-with-nlp.pdf"}]'),
('grace33', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["Mobile", "Flutter"]', 'draft', '[]'),
('henry34', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["ML", "AI"]', 'published', '[]'),
('henry34', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["Mobile", "Flutter"]', 'draft', '[{"url": "https://files.example.com/advanced-node.js-patterns.pdf"}]'),
('isla35', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["Backend", "Node.js"]', 'draft', '[{"url": "https://files.example.com/understanding-react-hooks.pdf"}]'),
('isla35', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["ML", "AI"]', 'published', '[{"url": "https://files.example.com/kubernetes-basics.pdf"}]'),
('jack36', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["UI", "Design"]', 'draft', '[{"url": "https://files.example.com/getting-started-with-nlp.pdf"}]'),
('jack36', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["Deep Learning", "Python"]', 'draft', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'),
('kara37', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["NLP", "AI"]', 'published', '[]'),
('kara37', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["UI", "Design"]', 'published', '[{"url": "https://files.example.com/ui-design-best-practices.pdf"}]'),
('liam38', 'Building Mobile Apps with Flutter', '{"text": "This is a detailed guide about Building Mobile Apps with Flutter."}', '["React", "JavaScript"]', 'published', '[]'),
('liam38', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["UI", "Design"]', 'published', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'),
('maya39', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["React", "JavaScript"]', 'draft', '[]'),
('maya39', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["Deep Learning", "Python"]', 'published', '[]'),
('noah40', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["Deep Learning", "Python"]', 'published', '[]'),
('noah40', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["Deep Learning", "Python"]', 'draft', '[]'),
('olivia41', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["Deep Learning", "Python"]', 'published', '[]'),
('olivia41', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["Deep Learning", "Python"]', 'draft', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'),
('peter42', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["NLP", "AI"]', 'draft', '[{"url": "https://files.example.com/getting-started-with-nlp.pdf"}]'),
('peter42', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["Mobile", "Flutter"]', 'published', '[{"url": "https://files.example.com/intro-to-machine-learning.pdf"}]'),
('queen43', 'Docker for Developers', '{"text": "This is a detailed guide about Docker for Developers."}', '["ML", "AI"]', 'published', '[{"url": "https://files.example.com/docker-for-developers.pdf"}]'),
('queen43', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["Python", "Pandas"]', 'draft', '[]'),
('rita44', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["Deep Learning", "Python"]', 'published', '[{"url": "https://files.example.com/ui-design-best-practices.pdf"}]'),
('rita44', 'Kubernetes Basics', '{"text": "This is a detailed guide about Kubernetes Basics."}', '["DevOps", "Cloud"]', 'published', '[{"url": "https://files.example.com/kubernetes-basics.pdf"}]'),
('samuel45', 'Understanding React Hooks', '{"text": "This is a detailed guide about Understanding React Hooks."}', '["React", "JavaScript"]', 'draft', '[{"url": "https://files.example.com/understanding-react-hooks.pdf"}]'),
('samuel45', 'Building Mobile Apps with Flutter', '{"text": "This is a detailed guide about Building Mobile Apps with Flutter."}', '["ML", "AI"]', 'published', '[{"url": "https://files.example.com/building-mobile-apps-with-flutter.pdf"}]'),
('tina46', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["Kubernetes", "Docker"]', 'draft', '[]'),
('tina46', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["UI", "Design"]', 'published', '[]'),
('ursula47', 'UI Design Best Practices', '{"text": "This is a detailed guide about UI Design Best Practices."}', '["UI", "Design"]', 'published', '[{"url": "https://files.example.com/ui-design-best-practices.pdf"}]'),
('ursula47', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["Deep Learning", "Python"]', 'published', '[]'),
('vera48', 'Getting Started with NLP', '{"text": "This is a detailed guide about Getting Started with NLP."}', '["ML", "AI"]', 'published', '[]'),
('vera48', 'Advanced Node.js Patterns', '{"text": "This is a detailed guide about Advanced Node.js Patterns."}', '["Python", "Pandas"]', 'published', '[{"url": "https://files.example.com/advanced-node.js-patterns.pdf"}]'),
('william49', 'Data Cleaning with Pandas', '{"text": "This is a detailed guide about Data Cleaning with Pandas."}', '["Kubernetes", "Docker"]', 'published', '[{"url": "https://files.example.com/data-cleaning-with-pandas.pdf"}]'),
('william49', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["DevOps", "Cloud"]', 'published', '[{"url": "https://files.example.com/intro-to-machine-learning.pdf"}]'),
('zoe50', 'Intro to Machine Learning', '{"text": "This is a detailed guide about Intro to Machine Learning."}', '["DevOps", "Cloud"]', 'published', '[]'),
('zoe50', 'Building Mobile Apps with Flutter', '{"text": "This is a detailed guide about Building Mobile Apps with Flutter."}', '["Deep Learning", "Python"]', 'published', '[]');

View File

@@ -1,6 +1,6 @@
import { SQL } from "bun";
import { afterAll, expect, test } from "bun:test";
import { isLinux } from "harness";
import { bunEnv, bunExe, isLinux, tempDirWithFiles } from "harness";
import path from "path";
const postgres = (...args) => new SQL(...args);
@@ -26,8 +26,9 @@ async function waitForPostgres(port) {
for (let i = 0; i < 3; i++) {
try {
const sql = new SQL(`postgres://bun_sql_test@localhost:${port}/bun_sql_test`, {
idle_timeout: 20,
max_lifetime: 60 * 30,
idleTimeout: 1,
connectionTimeout: 1,
maxLifetime: 1,
tls: {
ca: Bun.file(path.join(import.meta.dir, "docker-tls", "server.crt")),
},
@@ -64,7 +65,6 @@ async function startContainer(): Promise<{ port: number; containerName: string }
// Start the container
await execAsync(`${dockerCLI} run -d --name ${containerName} -p ${port}:5432 custom-postgres-tls`);
// Wait for PostgreSQL to be ready
await waitForPostgres(port);
return {
@@ -148,4 +148,196 @@ if (isDockerEnabled()) {
const result = (await sql`select 1 as x`)[0].x;
expect(result).toBe(1);
});
test("should not segfault under pressure #21351", async () => {
// we need at least the usename and port
await using sql = postgres(connectionString, {
max: 1,
idleTimeout: 1,
connectionTimeout: 1,
tls: {
rejectUnauthorized: false,
},
});
await sql`create table users (
id text not null,
created_at timestamp with time zone not null default now(),
name text null,
email text null,
identifier text not null default '-'::text,
role text null default 'CUSTOMER'::text,
phone text null,
bio jsonb null,
skills jsonb null default '[]'::jsonb,
privacy text null default 'PUBLIC'::text,
linkedin_url text null,
github_url text null,
facebook_url text null,
twitter_url text null,
picture jsonb null,
constraint users_pkey primary key (id),
constraint users_identifier_key unique (identifier)
) TABLESPACE pg_default;
create table posts (
id uuid not null default gen_random_uuid (),
created_at timestamp with time zone not null default now(),
user_id text null,
title text null,
content jsonb null,
tags jsonb null,
type text null default 'draft'::text,
attachments jsonb null default '[]'::jsonb,
updated_at timestamp with time zone null,
constraint posts_pkey primary key (id),
constraint posts_user_id_fkey foreign KEY (user_id) references users (id) on update CASCADE on delete CASCADE
) TABLESPACE pg_default;`.simple();
await sql.file(path.join(import.meta.dirname, "issue-21351.fixture.sql"));
const dir = tempDirWithFiles("import-meta-no-inline", {
"index.ts": `
import { SQL } from "bun";
const db = new SQL({
url: process.env.DATABASE_URL,
max: 1,
idleTimeout: 60 * 5,
maxLifetime: 60 * 15,
tls: {
ca: Bun.file(process.env.DATABASE_CA as string),
},
});
await db.connect();
const server = Bun.serve({
port: 0,
fetch: async (req) => {
try{
await Bun.sleep(100);
let fragment = db\`\`;
const searchs = await db\`
WITH cte AS (
SELECT
post.id,
post."content",
post.created_at AS "createdAt",
users."name" AS "userName",
users.id AS "userId",
users.identifier AS "userIdentifier",
users.picture AS "userPicture",
'{}'::json AS "group"
FROM posts post
INNER JOIN users
ON users.id = post.user_id
\${fragment}
ORDER BY post.created_at DESC
)
SELECT
*
FROM cte
-- LIMIT 5
\`;
return Response.json(searchs);
} catch {
return new Response(null, { status: 500 });
}
},
});
console.log(server.url.href);
`,
});
sql.end({ timeout: 0 });
async function bombardier(url, batchSize = 100, abortSignal) {
let batch = [];
for (let i = 0; i < 100_000 && !abortSignal.aborted; i++) {
//@ts-ignore
batch.push(fetch(url, { signal: abortSignal }).catch(() => {}));
if (batch.length > batchSize) {
await Promise.all(batch);
batch = [];
}
}
await Promise.all(batch);
}
let failed = false;
function spawnServer(controller) {
return new Promise(async (resolve, reject) => {
const server = Bun.spawn([bunExe(), "index.ts"], {
stdin: "ignore",
stdout: "pipe",
stderr: "pipe",
cwd: dir,
env: {
...bunEnv,
BUN_DEBUG_QUIET_LOGS: "1",
DATABASE_URL: connectionString,
DATABASE_CA: path.join(import.meta.dir, "docker-tls", "server.crt"),
},
onExit(proc, exitCode, signalCode, error) {
// exit handler
if (exitCode !== 0) {
failed = true;
controller.abort();
}
},
});
const reader = server.stdout.getReader();
const errorReader = server.stderr.getReader();
const decoder = new TextDecoder();
async function outputData(reader, type = "log") {
while (true) {
const { done, value } = await reader.read();
if (done) break;
if (value) {
if (type === "error") {
console.error(decoder.decode(value));
} else {
console.log(decoder.decode(value));
}
}
}
}
const url = decoder.decode((await reader.read()).value);
resolve({ url, kill: () => server.kill() });
outputData(reader);
errorReader.read().then(({ value }) => {
if (value) {
console.error(decoder.decode(value));
failed = true;
}
outputData(errorReader, "error");
});
});
}
async function spawnRestarts(controller) {
for (let i = 0; i < 20 && !controller.signal.aborted; i++) {
await Bun.$`${dockerCLI} restart ${container.containerName}`.nothrow().quiet();
await Bun.sleep(500);
}
try {
controller.abort();
} catch {}
}
const controller = new AbortController();
const { promise, resolve, reject } = Promise.withResolvers();
const server = (await spawnServer(controller)) as { url: string; kill: () => void };
controller.signal.addEventListener("abort", () => {
if (!failed) resolve();
else reject(new Error("Server crashed"));
server.kill();
});
bombardier(server.url, 100, controller.signal);
await Bun.sleep(1000);
spawnRestarts(controller);
await promise;
}, 30_000);
}