Skip to content

Commit 00d1f76

Browse files
committed
Rename server to backend: deprecate server param, add backend constructors
Phase 9A: Migrate server-side data API from server to backend param. API changes: - Add backendV8(), backendDf(), backendDt() constructors (exported) - Deprecate server param with warning, mapping values to backend objects - Accept custom S3 backends via is.object() validation - Warn when server-only backend is used outside Shiny (preRenderHook) File/class renames (server -> backend): - R/server-*.R -> R/backend-*.R (4 files) - tests/testthat/test-server-*.R -> tests/testthat/test-backend-*.R (4 files) - S3 classes: reactable_server* -> reactable_backend* - Delete R/backends.R, move each constructor into its own backend file - Remove unused isServerBackend() helper Documentation: - Add required packages note to backendDuckDB() (arrow for client, duckdb+DBI for server) - Add experimental callout notes to DuckDB vignettes - Rename vignette titles: 'Client-Side DuckDB' -> 'DuckDB Backend' - Add install.packages('arrow') to DuckDB vignette - Update pkgdown nav text - New man pages: backendDf.Rd, backendDt.Rd, backendV8.Rd Examples: - Add shiny-server-data-duckdb-1m.R (1M rows) and -10m.R (10M rows) - Update existing duckdb example to use backendDuckDB() without explicit mode - Update shiny-server-data-100k.R and -1m.R to use backend param Implementation plan: - Check off completed 9A items (9.1-9.5.1, 9.6, 9.7) - Add 9.14: benchmark V8 vs DuckDB server-side performance - Add 9.13: resolve custom backend + client mode questions
1 parent c539009 commit 00d1f76

31 files changed

+574
-231
lines changed

NAMESPACE

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,20 @@
11
# Generated by roxygen2: do not edit by hand
22

33
S3method(as.tags,reactable)
4+
S3method(reactableServerData,default)
5+
S3method(reactableServerData,reactable_backendDf)
6+
S3method(reactableServerData,reactable_backendDt)
7+
S3method(reactableServerData,reactable_backendDuckdb)
8+
S3method(reactableServerData,reactable_backendV8)
9+
S3method(reactableServerInit,default)
10+
S3method(reactableServerInit,reactable_backendDt)
11+
S3method(reactableServerInit,reactable_backendDuckdb)
12+
S3method(reactableServerInit,reactable_backendV8)
413
export(JS)
14+
export(backendDf)
15+
export(backendDt)
516
export(backendDuckDB)
17+
export(backendV8)
618
export(colDef)
719
export(colFormat)
820
export(colGroup)

R/server-df.R renamed to R/backend-df.R

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,22 @@
1-
serverDf <- function() {
2-
structure(list(), class = "reactable_serverDf")
1+
#' Data Frame Server Backend
2+
#'
3+
#' Uses pure R data frame operations for table data processing in Shiny apps.
4+
#' Does not require the V8 package.
5+
#'
6+
#' @return A backend object to pass to the `backend` argument of [reactable()].
7+
#'
8+
#' @examples
9+
#' \dontrun{
10+
#' reactable(data, backend = backendDf())
11+
#' }
12+
#'
13+
#' @export
14+
backendDf <- function() {
15+
structure(list(), class = "reactable_backendDf")
316
}
417

5-
reactableServerData.reactable_serverDf <- function(
18+
#' @exportS3Method
19+
reactableServerData.reactable_backendDf <- function(
620
x,
721
data = NULL,
822
columns = NULL,

R/server-dt.R renamed to R/backend-dt.R

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
1-
serverDt <- function() {
2-
structure(list(), class = "reactable_serverDt")
1+
#' data.table Server Backend
2+
#'
3+
#' Uses the data.table package for table data processing in Shiny apps.
4+
#' Requires the data.table package.
5+
#'
6+
#' @return A backend object to pass to the `backend` argument of [reactable()].
7+
#'
8+
#' @examples
9+
#' \dontrun{
10+
#' reactable(data, backend = backendDt())
11+
#' }
12+
#'
13+
#' @export
14+
backendDt <- function() {
15+
structure(list(), class = "reactable_backendDt")
316
}
417

5-
reactableServerInit.reactable_serverDt <- function(...) {
18+
#' @exportS3Method
19+
reactableServerInit.reactable_backendDt <- function(...) {
620
if (!require("data.table", quietly = TRUE)) {
721
stop('The data.table package must be installed to use the "dt" server backend.
822
Do you need to run `install.packages("data.table")`?', call. = FALSE)
923
}
1024
}
1125

12-
reactableServerData.reactable_serverDt <- function(
26+
#' @exportS3Method
27+
reactableServerData.reactable_backendDt <- function(
1328
x,
1429
data = NULL,
1530
columns = NULL,
Lines changed: 95 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,117 @@
1+
#' DuckDB Backend
2+
#'
3+
#' Use DuckDB for table data processing. In static documents (R Markdown, Quarto),
4+
#' uses DuckDB-WASM in the browser for client-side sorting, filtering, and pagination
5+
#' via SQL. In Shiny, uses the DuckDB R package on the server.
6+
#'
7+
#' @param mode One of `"auto"`, `"client"`, or `"server"`:
8+
#' \describe{
9+
#' \item{`"auto"` (default)}{Automatically detects the rendering context.
10+
#' Uses DuckDB-WASM client-side in static documents, or the DuckDB R package
11+
#' server-side in Shiny apps.}
12+
#' \item{`"client"`}{Always uses DuckDB-WASM in the browser. Data is serialized
13+
#' as Arrow IPC and sent to the client.}
14+
#' \item{`"server"`}{Always uses the DuckDB R package on the server. Only works
15+
#' in Shiny apps.}
16+
#' }
17+
#' @param format Data format for client-side mode. One of `"auto"`, `"arrow"`, or `"parquet"`:
18+
#' \describe{
19+
#' \item{`"auto"` (default)}{Uses Arrow IPC for small datasets and Parquet sidecar
20+
#' files for larger datasets (over ~20 MB). Parquet files are served via HTTP range
21+
#' requests so the browser only downloads the data it needs for each page.}
22+
#' \item{`"arrow"`}{Always embeds data as base64-encoded Arrow IPC in the HTML document.}
23+
#' \item{`"parquet"`}{Always writes a Parquet sidecar file alongside the HTML document.
24+
#' Only works with `self_contained: false` output.}
25+
#' }
26+
#'
27+
#' The `format` option only applies in client-side mode (DuckDB-WASM in the browser).
28+
#' In server mode, data stays in R memory and this option is ignored.
29+
#'
30+
#' @return A backend object to pass to the `backend` argument of [reactable()].
31+
#'
32+
#' @note
33+
#' **Required packages:**
34+
#' \itemize{
35+
#' \item Client mode requires the \pkg{arrow} package.
36+
#' \item Server mode requires the \pkg{duckdb} and \pkg{DBI} packages.
37+
#' }
38+
#'
39+
#' The DuckDB backend does not support:
40+
#' \itemize{
41+
#' \item Custom `searchMethod` or column `filterMethod` (SQL-based search/filter is used instead)
42+
#' \item Custom JavaScript `aggregate` functions (use built-in aggregate names like `"sum"`, `"mean"`)
43+
#' \item R function renderers for `cell`, `details`, `style`, `class`, `rowClass`, `rowStyle`
44+
#' (use `JS()` function renderers instead)
45+
#' \item Row selection
46+
#' }
47+
#'
48+
#' @examples
49+
#' \dontrun{
50+
#' # Auto-detection: WASM in static docs, server in Shiny
51+
#' reactable(data, backend = backendDuckDB())
52+
#'
53+
#' # Force client-side (DuckDB-WASM)
54+
#' reactable(data, backend = backendDuckDB("client"))
55+
#'
56+
#' # Force server-side (DuckDB R package, Shiny only)
57+
#' reactable(data, backend = backendDuckDB("server"))
58+
#'
59+
#' # Always use Parquet sidecar files (for large datasets)
60+
#' reactable(data, backend = backendDuckDB(format = "parquet"))
61+
#'
62+
#' # Always embed Arrow IPC (for self-contained output)
63+
#' reactable(data, backend = backendDuckDB(format = "arrow"))
64+
#' }
65+
#'
66+
#' @export
67+
backendDuckDB <- function(mode = c("auto", "client", "server"),
68+
format = c("auto", "arrow", "parquet")) {
69+
mode <- match.arg(mode)
70+
format <- match.arg(format)
71+
structure(list(mode = mode, format = format), class = "reactable_backendDuckDB")
72+
}
73+
74+
isDuckDBBackend <- function(x) {
75+
inherits(x, "reactable_backendDuckDB")
76+
}
77+
78+
# Resolve "auto" mode to "client" or "server" based on rendering context
79+
resolveDuckDBMode <- function(backend) {
80+
if (backend$mode != "auto") return(backend$mode)
81+
session <- NULL
82+
if (requireNamespace("shiny", quietly = TRUE)) {
83+
session <- shiny::getDefaultReactiveDomain()
84+
}
85+
if (!is.null(session)) "server" else "client"
86+
}
87+
188
# DuckDB server backend for Shiny. Uses the DuckDB R package for fast SQL-based
289
# sorting, filtering, searching, and pagination. Data stays on the R server.
390
#
4-
# Uses duckdb_register() for zero-copy data access DuckDB reads directly from
91+
# Uses duckdb_register() for zero-copy data access -- DuckDB reads directly from
592
# the R data frame's memory without copying.
693

7-
serverDuckdb <- function() {
94+
backendDuckdbServer <- function() {
895
private <- new.env(parent = emptyenv())
9-
structure(list(private = private), class = "reactable_serverDuckdb")
96+
structure(list(private = private), class = "reactable_backendDuckdb")
1097
}
1198

12-
reactableServerInit.reactable_serverDuckdb <- function(x, data = NULL, columns = NULL, ...) {
99+
#' @exportS3Method
100+
reactableServerInit.reactable_backendDuckdb <- function(x, data = NULL, columns = NULL, ...) {
13101
if (!requireNamespace("duckdb", quietly = TRUE) || !requireNamespace("DBI", quietly = TRUE)) {
14102
stop('The duckdb and DBI packages must be installed to use the DuckDB server backend.\n',
15103
'Install with: install.packages(c("duckdb", "DBI"))', call. = FALSE)
16104
}
17105

18106
con <- DBI::dbConnect(duckdb::duckdb())
19-
# Zero-copy registration DuckDB reads directly from R data frame memory
107+
# Zero-copy registration - DuckDB reads directly from R data frame memory
20108
duckdb::duckdb_register(con, "reactable_data", data)
21109
x$private$con <- con
22110
x$private$columns <- columns
23111
}
24112

25-
reactableServerData.reactable_serverDuckdb <- function(
113+
#' @exportS3Method
114+
reactableServerData.reactable_backendDuckdb <- function(
26115
x,
27116
data = NULL,
28117
columns = NULL,

R/server-v8.R renamed to R/backend-v8.R

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,17 @@
1-
serverV8 <- function() {
1+
#' V8 Server Backend
2+
#'
3+
#' Uses V8 (server-side JavaScript) for table data processing in Shiny apps.
4+
#' This is the default server-side backend.
5+
#'
6+
#' @return A backend object to pass to the `backend` argument of [reactable()].
7+
#'
8+
#' @examples
9+
#' \dontrun{
10+
#' reactable(data, backend = backendV8())
11+
#' }
12+
#'
13+
#' @export
14+
backendV8 <- function() {
215
private <- new.env(parent = emptyenv())
316

417
structure(list(
@@ -12,7 +25,7 @@ serverV8 <- function() {
1225
) {
1326

1427
if (!requireNamespace("V8", quietly = TRUE)) {
15-
stop('The V8 package must be installed to use `reactable(server = TRUE)`.
28+
stop('The V8 package must be installed to use `reactable(backend = backendV8())`.
1629
Do you need to run `install.packages("V8")`?', call. = FALSE)
1730
}
1831
# Initialize V8 and set initial props to reduce overhead of JSON serialization,
@@ -41,7 +54,7 @@ Do you need to run `install.packages("V8")`?', call. = FALSE)
4154
ctx$call("Reactable.setInitialProps", toJSON(input))
4255
private$ctx <- ctx
4356
end <- Sys.time()
44-
debugLog(sprintf("(serverV8) init time: %s", format(end - start)))
57+
debugLog(sprintf("(backendV8) init time: %s", format(end - start)))
4558
},
4659

4760
data = function(
@@ -82,7 +95,7 @@ Do you need to run `install.packages("V8")`?', call. = FALSE)
8295
input <- toJSON(input)
8396
result <- private$ctx$call("Reactable.renderToData", input)
8497
end <- Sys.time()
85-
debugLog(sprintf("(serverV8) Reactable.renderToData() time: %s", format(end - start)))
98+
debugLog(sprintf("(backendV8) Reactable.renderToData() time: %s", format(end - start)))
8699
result
87100
}, error = function(e) {
88101
stop(sprintf("Failed to server render table:\n%s", e), call. = FALSE)
@@ -101,13 +114,15 @@ Do you need to run `install.packages("V8")`?', call. = FALSE)
101114
maxRowCount = result$maxRowCount
102115
))
103116
}
104-
), class = "reactable_serverV8")
117+
), class = "reactable_backendV8")
105118
}
106119

107-
reactableServerInit.reactable_serverV8 <- function(x, ...) {
120+
#' @exportS3Method
121+
reactableServerInit.reactable_backendV8 <- function(x, ...) {
108122
x$init(...)
109123
}
110124

111-
reactableServerData.reactable_serverV8 <- function(x, ...) {
125+
#' @exportS3Method
126+
reactableServerData.reactable_backendV8 <- function(x, ...) {
112127
x$data(...)
113128
}

R/backends.R

Lines changed: 0 additions & 80 deletions
This file was deleted.

0 commit comments

Comments
 (0)