diff --git a/.Rbuildignore b/.Rbuildignore index b5da25b..093edc8 100644 --- a/.Rbuildignore +++ b/.Rbuildignore @@ -16,3 +16,4 @@ ^Meta$ ^\.automerge$ ^codecov\.yml$ +^cran-comments\.md$ diff --git a/DESCRIPTION b/DESCRIPTION index 5003242..4d238a7 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -9,8 +9,8 @@ Authors@R: c( comment = c(ROR = "03wc8by49")) ) Description: A WebSocket-based implementation of the 'automerge-repo' - synchronization protocol used by 'sync.automerge.org'. Acts as a sync - server, enabling R to serve as a synchronization hub for 'Automerge' + synchronization protocol used by 'sync.automerge.org'. Acts as a sync + server, enabling 'R' to serve as a synchronization hub for 'Automerge' clients in 'JavaScript', 'Rust', and other languages, and as a client for fetching, editing, and synchronizing documents hosted on remote servers. @@ -27,10 +27,8 @@ Imports: later, nanonext (>= 1.8.1), promises, - secretbase (>= 1.2.0), - utils + secretbase (>= 1.3.0) Suggests: - httpuv, openssl, testthat (>= 3.0.0) Config/Needs/website: tidyverse/tidytemplate diff --git a/LICENSE b/LICENSE index 0587aa0..4189ca6 100644 --- a/LICENSE +++ b/LICENSE @@ -1,2 +1,2 @@ -YEAR: 2025 +YEAR: 2026 COPYRIGHT HOLDER: autosync authors diff --git a/NAMESPACE b/NAMESPACE index 8975dc2..d699a83 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,8 +1,8 @@ # Generated by roxygen2: do not edit by hand -S3method(print,sync_client) -S3method(print,sync_doc) -S3method(print,sync_server) +S3method(print,autosync_client) +S3method(print,autosync_doc) +S3method(print,autosync_server) export(auth_config) export(create_document) export(generate_document_id) @@ -28,9 +28,14 @@ importFrom(httr2,oauth_client) importFrom(httr2,oauth_flow_auth_code) importFrom(httr2,oauth_redirect_uri) importFrom(httr2,oauth_server_metadata) +importFrom(jose,jwt_decode_sig) +importFrom(jose,read_jwk) importFrom(later,later) importFrom(later,run_now) +importFrom(nanonext,handler_ws) importFrom(nanonext,http_server) +importFrom(nanonext,is_error_value) +importFrom(nanonext,ncurl) importFrom(nanonext,random) importFrom(nanonext,recv) importFrom(nanonext,recv_aio) @@ -40,6 +45,7 @@ importFrom(nanonext,stream) importFrom(nanonext,tls_config) importFrom(nanonext,unresolved) importFrom(nanonext,write_cert) +importFrom(promises,then) importFrom(secretbase,base58dec) importFrom(secretbase,base58enc) importFrom(secretbase,base64dec) diff --git a/NEWS.md b/NEWS.md index 82368a5..447650e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,16 +1,6 @@ # autosync (development version) -* The `amsync_*` connector functions were renamed `sync_*`: `sync_server()`, - `sync_client()`, `sync_fetch()` and `sync_token()` (with S3 classes - `sync_server`, `sync_client` and `sync_doc`). -* `sync_client()` now opens a connection only and no longer takes a `doc_id`. - Open one or more live documents over the connection with the new - `$open_doc(doc_id)` method, which returns a `sync_doc` handle exposing - `$doc`, `$push()`, `$active` and `$close()`. A single connection can sync - several documents, and `$close()` tears them all down. -* Project browsing and live editing moved to the `shinysync` package - (`project_open()`, `project_app()` and `project_edit()`); autosync no longer - depends on `shiny` or `bslib`. +* Initial CRAN release. # autosync 0.0.1 diff --git a/R/auth.R b/R/auth.R index b4acb64..b1e22a6 100644 --- a/R/auth.R +++ b/R/auth.R @@ -28,9 +28,9 @@ oidc_issuer <- function() { #' @keywords internal discover_jwks_uri <- function(issuer) { config_url <- paste0(issuer, "/.well-known/openid-configuration") - resp <- nanonext::ncurl(config_url, timeout = 5000L) + resp <- ncurl(config_url, timeout = 5000L) - if (nanonext::is_error_value(resp$data) || resp$status != 200L) { + if (is_error_value(resp$data) || resp$status != 200L) { stop("Failed to fetch OIDC configuration from: ", config_url) } @@ -53,13 +53,13 @@ discover_jwks_uri <- function(issuer) { #' #' @keywords internal fetch_jwks <- function(jwks_uri) { - resp <- nanonext::ncurl( + resp <- ncurl( jwks_uri, timeout = 5000L, response = "Cache-Control" ) - if (nanonext::is_error_value(resp$data) || resp$status != 200L) { + if (is_error_value(resp$data) || resp$status != 200L) { stop("Failed to fetch JWKS from: ", jwks_uri) } @@ -76,7 +76,7 @@ fetch_jwks <- function(jwks_uri) { next } key <- tryCatch( - jose::read_jwk(jsonenc(jwk)), + read_jwk(jsonenc(jwk)), error = function(e) NULL ) if (!is.null(key)) { @@ -167,7 +167,7 @@ validate_token <- function( } header <- tryCatch( - jsondec(jose::base64url_decode(parts[1L])), + jsondec(base64dec(parts[1L], url = TRUE)), error = function(e) NULL ) if (is.null(header) || is.null(header$kid)) { @@ -189,7 +189,7 @@ validate_token <- function( # Verify signature and decode claims claims <- tryCatch( - jose::jwt_decode_sig(token, key), + jwt_decode_sig(token, key), error = function(e) { msg <- conditionMessage(e) if (grepl("expired", msg, ignore.case = TRUE)) { @@ -329,7 +329,7 @@ validate_token <- function( #' @param custom_validator Function(claims) returning TRUE/FALSE for #' custom validation logic. Receives the decoded JWT claims as a list. #' -#' @return An amsync_auth_config object. +#' @return An object of class `"autosync_auth_config"`. #' #' @examples #' # Google (default issuer) @@ -380,7 +380,7 @@ auth_config <- function( allowed_domains = allowed_domains, custom_validator = custom_validator ), - class = "amsync_auth_config" + class = "autosync_auth_config" ) } @@ -496,7 +496,7 @@ sync_token <- function( #' Extracts and validates a Bearer token (JWT) from the Authorization header #' of the WebSocket upgrade request. #' -#' @param auth_config An amsync_auth_config object. +#' @param auth_config A `autosync_auth_config` object. #' @param headers Named list of HTTP request headers. #' #' @return List with `valid` (logical), `email` (character or NULL), diff --git a/R/autosync-package.R b/R/autosync-package.R index 4ddffe3..d469656 100644 --- a/R/autosync-package.R +++ b/R/autosync-package.R @@ -1,7 +1,7 @@ -#' autosync: 'Automerge' Sync Server and Client for R +#' autosync: 'Automerge' Sync Server and Client #' #' A WebSocket-based implementation of the 'automerge-repo' synchronization -#' protocol used by 'sync.automerge.org'. Acts as a sync server, enabling R to +#' protocol used by 'sync.automerge.org'. Acts as a sync server, enabling 'R' to #' serve as a synchronization hub for 'Automerge' clients in 'JavaScript', #' 'Rust', and other languages, and as a client for fetching, editing, and #' synchronizing documents hosted on remote servers. @@ -47,8 +47,10 @@ #' #' @importFrom automerge am_create am_get am_keys am_length am_load am_save am_sync_decode am_sync_encode am_sync_state am_sync_state_decode am_sync_state_encode AM_ROOT #' @importFrom httr2 oauth_client oauth_flow_auth_code oauth_redirect_uri oauth_server_metadata +#' @importFrom jose jwt_decode_sig read_jwk #' @importFrom later later run_now -#' @importFrom nanonext http_server random recv recv_aio send stop_aio stream tls_config unresolved write_cert +#' @importFrom nanonext handler_ws http_server is_error_value ncurl random recv recv_aio send stop_aio stream tls_config unresolved write_cert +#' @importFrom promises then #' @importFrom secretbase base64enc base64dec base58enc base58dec cborenc cbordec jsondec jsonenc #' @importFrom utils str "_PACKAGE" diff --git a/R/client.R b/R/client.R index 4eedb3c..2b6f36a 100644 --- a/R/client.R +++ b/R/client.R @@ -84,18 +84,18 @@ join_msg <- function(peer_id) { #' for and send local changes for every open document. This is a cheap no-op #' when there are no changes. #' -#' @return An environment of class `"sync_client"` with reference semantics, +#' @return An environment of class `"autosync_client"` with reference semantics, #' representing the connection: #' \describe{ #' \item{`open_doc(doc_id, timeout)`}{Open a live document over this -#' connection and return a `sync_doc` handle for it (see below). +#' connection and return a `autosync_doc` handle for it (see below). #' Repeated calls for the same `doc_id` reuse the document already open #' on the connection rather than requesting it again.} #' \item{`close()`}{Disconnect and stop syncing all open documents.} #' \item{`active`}{Logical, whether the connection is active.} #' } #' -#' A `sync_doc` handle returned by `$open_doc()` is itself an environment +#' A `autosync_doc` handle returned by `$open_doc()` is itself an environment #' with: #' \describe{ #' \item{`doc`}{The live automerge document, kept in sync with the server.} @@ -269,7 +269,7 @@ sync_client <- function( # Track the pending aio so close() can settle it before tearing down the # stream, leaving no stale promise continuation for a later run_now to hit. client$recv_aio <- aio - promises::then( + then( aio, onFulfilled = function(value) { tryCatch( @@ -337,7 +337,7 @@ sync_client <- function( }, handle ) - class(handle) <- "sync_doc" + class(handle) <- "autosync_doc" handle } @@ -430,7 +430,7 @@ sync_client <- function( invisible() } - class(client) <- "sync_client" + class(client) <- "autosync_client" on.exit() # stream now owned by client$close # Start the async loops @@ -441,7 +441,7 @@ sync_client <- function( } #' @export -print.sync_client <- function(x, ...) { +print.autosync_client <- function(x, ...) { cat("Automerge Sync Connection\n") cat(" Server:", x$url, "\n") cat(" Documents:", length(x$documents), "\n") @@ -450,7 +450,7 @@ print.sync_client <- function(x, ...) { } #' @export -print.sync_doc <- function(x, ...) { +print.autosync_doc <- function(x, ...) { cat("Automerge Document\n") cat(" Document:", x$doc_id, "\n") cat(" Active:", x$active, "\n") diff --git a/R/handlers.R b/R/handlers.R index f318485..fadb02c 100644 --- a/R/handlers.R +++ b/R/handlers.R @@ -17,7 +17,7 @@ eval_share <- function(share, client_id, doc_id) { #' #' Routes incoming CBOR messages to the appropriate handler. #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param client_id Client's peer ID (or temp_id if pre-handshake). #' @param temp_id Temporary connection ID. #' @param raw_msg Raw CBOR message bytes. @@ -62,7 +62,7 @@ handle_message <- function(server, client_id, temp_id, raw_msg) { #' Handle join handshake message #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param temp_id Temporary connection ID. #' @param msg Decoded join message. #' @@ -143,7 +143,7 @@ handle_join <- function(server, temp_id, msg) { #' #' Creates sync state if needed and sends a sync message. #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param client_id Client's peer ID. #' @param doc_id Document ID. #' @param doc Automerge document object. @@ -178,7 +178,7 @@ sync_to_peer <- function(server, client_id, doc_id, doc) { #' Handle sync or request message #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param client_id Client's peer ID. #' @param msg Decoded sync/request message. #' @param is_request Logical, TRUE for request messages. @@ -275,7 +275,7 @@ handle_sync <- function(server, client_id, msg, is_request) { #' Handle leave message #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param client_id Client's peer ID. #' @param msg Decoded leave message. #' @@ -292,7 +292,7 @@ handle_leave <- function(server, client_id, msg) { #' - Point-to-point: message with targetId forwards to specific peer #' - Broadcast: message with documentId relays to all peers on that document #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param client_id Client's peer ID. #' @param msg Decoded ephemeral message. #' @@ -322,7 +322,7 @@ handle_ephemeral <- function(server, client_id, msg) { #' Add a client to a document's peer list #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param doc_id Document ID. #' @param client_id Client's peer ID. #' @@ -339,7 +339,7 @@ add_doc_peer <- function(server, doc_id, client_id) { #' Remove a client from a document's peer list #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param doc_id Document ID. #' @param client_id Client's peer ID. #' @@ -359,7 +359,7 @@ remove_doc_peer <- function(server, doc_id, client_id) { #' Remove a client from all document peer lists #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param client_id Client's peer ID. #' #' @noRd @@ -372,7 +372,7 @@ remove_peer_from_all_docs <- function(server, client_id) { #' Broadcast ephemeral message to all peers on a document #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param sender_client_id ID of the client who sent the message. #' @param doc_id Document ID. #' @param msg The ephemeral message to broadcast. @@ -399,7 +399,7 @@ broadcast_ephemeral <- function(server, sender_client_id, doc_id, msg) { #' Handle error message from client #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param client_id Client's peer ID. #' @param msg Decoded error message. #' @@ -411,7 +411,7 @@ handle_error <- function(server, client_id, msg) { #' Clean up resources when client disconnects #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param client_id Client's peer ID. #' #' @noRd @@ -437,7 +437,7 @@ handle_disconnect <- function(server, client_id) { #' Send message to specific peer #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param peer_id Target peer ID. #' @param msg List to encode and send. #' @@ -452,7 +452,7 @@ send_to_peer <- function(server, peer_id, msg) { #' Send error message to peer #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param peer_id Target peer ID. #' @param message Error message string. #' @param temp_id Optional temporary connection ID for pre-handshake errors. @@ -478,7 +478,7 @@ send_error <- function(server, peer_id, message, temp_id = NULL) { #' Send doc-unavailable message #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param client_id Client's peer ID. #' @param doc_id Document ID. #' @@ -500,7 +500,7 @@ send_unavailable <- function(server, client_id, doc_id) { #' Called when a document is created via [create_document()] after peering is #' already established. #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param doc_id Document ID. #' @param doc Automerge document object. #' @@ -517,7 +517,7 @@ announce_new_document <- function(server, doc_id, doc) { #' Broadcast sync changes to all connected peers #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param sender_client_id ID of the client who sent the change. #' @param doc_id Document ID. #' @param doc Automerge document object. diff --git a/R/server.R b/R/server.R index 86f9b56..0608410 100644 --- a/R/server.R +++ b/R/server.R @@ -36,7 +36,7 @@ #' or `FALSE` (deny access). Called per client and per document. #' } #' -#' @return A sync_server object inheriting from 'nanoServer', with +#' @return An autosync_server object inheriting from 'nanoServer', with #' `$start()` and `$close()` methods. #' #' @details @@ -176,7 +176,7 @@ sync_server <- function( } } - ws_handler <- nanonext::handler_ws( + ws_handler <- handler_ws( path = "/", on_message = on_message, on_open = on_open, @@ -184,14 +184,14 @@ sync_server <- function( textframes = FALSE ) - server <- nanonext::http_server( + server <- http_server( url = url, handlers = list(ws_handler), tls = tls ) attr(server, "sync") <- state - class(server) <- c("sync_server", class(server)) + class(server) <- c("autosync_server", class(server)) server } @@ -200,11 +200,17 @@ sync_server <- function( #' #' Retrieves an Automerge document by its ID. #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param doc_id Document ID string. #' #' @return Automerge document object, or NULL if not found. #' +#' @examplesIf interactive() +#' server <- sync_server() +#' doc_id <- create_document(server) +#' get_document(server, doc_id) +#' server$close() +#' #' @export get_document <- function(server, doc_id) { attr(server, "sync")$documents[[doc_id]] @@ -214,10 +220,16 @@ get_document <- function(server, doc_id) { #' #' Returns the IDs of all documents currently loaded in the server. #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' #' @return Character vector of document IDs. #' +#' @examplesIf interactive() +#' server <- sync_server() +#' create_document(server) +#' list_documents(server) +#' server$close() +#' #' @export list_documents <- function(server) { ls(attr(server, "sync")$documents) @@ -227,11 +239,16 @@ list_documents <- function(server) { #' #' Creates a new empty Automerge document and registers it with the server. #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param doc_id Optional document ID. If NULL, generates a new ID. #' #' @return Document ID string. #' +#' @examplesIf interactive() +#' server <- sync_server() +#' doc_id <- create_document(server) +#' server$close() +#' #' @export create_document <- function(server, doc_id = NULL) { state <- attr(server, "sync") @@ -248,16 +265,16 @@ create_document <- function(server, doc_id = NULL) { doc_id } -#' Print method for sync_server +#' Print method for autosync_server #' -#' @param x A sync_server object. +#' @param x An autosync_server object. #' @param ... Ignored. #' #' @return Invisibly returns x. #' #' @keywords internal #' @export -print.sync_server <- function(x, ...) { +print.autosync_server <- function(x, ...) { state <- attr(x, "sync") cat("Automerge Sync Server\n") cat(" URL:", x$url, "\n") diff --git a/R/storage.R b/R/storage.R index d79497f..9a931f3 100644 --- a/R/storage.R +++ b/R/storage.R @@ -2,7 +2,7 @@ #' Save a document to disk #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' @param doc_id Document ID string. #' @param doc Automerge document object. #' @@ -17,7 +17,7 @@ save_document <- function(server, doc_id, doc) { #' Loads all .automerge files from the server's data directory #' into the documents environment. #' -#' @param server A sync_server object. +#' @param server An autosync_server object. #' #' @noRd load_all_documents <- function(server) { diff --git a/R/utils.R b/R/utils.R index 8173f16..113e057 100644 --- a/R/utils.R +++ b/R/utils.R @@ -7,6 +7,9 @@ #' #' @return Character string (Base58Check encoded). #' +#' @examples +#' generate_document_id() +#' #' @export generate_document_id <- function() base58enc(random(16L, convert = FALSE)) @@ -37,7 +40,7 @@ generate_peer_id <- function() base64enc(random(16L, convert = FALSE)) #' @param server Server state environment. #' @param id Connection ID (temp_id or client_id). #' -#' @keywords internal +#' @noRd close_connection <- function(server, id) { conn <- server$connections[[id]] if (!is.null(conn) && !is.null(conn$ws)) { diff --git a/README.md b/README.md index c9f0022..f8c6966 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,14 @@ R sync server and client for [Automerge](https://automerge.org/) CRDT documents. ## Installation +Install the released version from CRAN: + +``` r +install.packages("autosync") +``` + +Or the development version from GitHub: + ``` r pak::pak("shikokuchuo/autosync") ``` @@ -168,7 +176,8 @@ const repo = new Repo({ ## Client -Fetch documents from any automerge-repo sync server: +Fetch a document from any automerge-repo sync server in a single, +one-off retrieval over a throwaway connection: ``` r # Fetch from public sync server @@ -181,6 +190,31 @@ str(doc) doc <- sync_fetch("wss://sync.automerge.org", "your-document-id", verbose = TRUE) ``` +### Live connections + +For a persistent connection that keeps documents in sync — receiving +real-time updates from other peers and flushing local changes — use +`sync_client()`. Several documents can share a single connection, each +opened with `$open_doc()`: + +``` r +conn <- sync_client("wss://sync.automerge.org") + +# Open a live document over the connection +doc <- conn$open_doc("your-document-id") +automerge::am_keys(doc$doc) + +# Make local changes and push them to the server +automerge::am_put(doc$doc, automerge::AM_ROOT, "key", "value") +doc$push() + +# Open another document over the same connection +other <- conn$open_doc("another-document-id") + +# Disconnect (closes every document on the connection) +conn$close() +``` + ## Utilities Generate document IDs compatible with automerge-repo: diff --git a/cran-comments.md b/cran-comments.md new file mode 100644 index 0000000..97b8211 --- /dev/null +++ b/cran-comments.md @@ -0,0 +1,11 @@ +## R CMD check results + +0 errors | 0 warnings | 0 notes + +* This is a new release. + +## Method references + +There are no published references describing the methods in this package. It +implements the publicly documented 'automerge-repo' WebSocket synchronization +protocol (). diff --git a/man/auth_config.Rd b/man/auth_config.Rd index 8560a44..e9ac4a6 100644 --- a/man/auth_config.Rd +++ b/man/auth_config.Rd @@ -34,7 +34,7 @@ a token is rejected unless it carries an \code{email} claim with custom validation logic. Receives the decoded JWT claims as a list.} } \value{ -An amsync_auth_config object. +An object of class \code{"autosync_auth_config"}. } \description{ Creates a configuration object for enabling OIDC JWT authentication diff --git a/man/authenticate_header.Rd b/man/authenticate_header.Rd index f88f2cf..38d387d 100644 --- a/man/authenticate_header.Rd +++ b/man/authenticate_header.Rd @@ -7,7 +7,7 @@ authenticate_header(auth_config, headers) } \arguments{ -\item{auth_config}{An amsync_auth_config object.} +\item{auth_config}{A \code{autosync_auth_config} object.} \item{headers}{Named list of HTTP request headers.} } diff --git a/man/autosync-package.Rd b/man/autosync-package.Rd index 72de482..7f2b6dc 100644 --- a/man/autosync-package.Rd +++ b/man/autosync-package.Rd @@ -4,10 +4,10 @@ \name{autosync-package} \alias{autosync-package} \alias{autosync} -\title{autosync: 'Automerge' Sync Server and Client for R} +\title{autosync: 'Automerge' Sync Server and Client} \description{ A WebSocket-based implementation of the 'automerge-repo' synchronization -protocol used by 'sync.automerge.org'. Acts as a sync server, enabling R to +protocol used by 'sync.automerge.org'. Acts as a sync server, enabling 'R' to serve as a synchronization hub for 'Automerge' clients in 'JavaScript', 'Rust', and other languages, and as a client for fetching, editing, and synchronizing documents hosted on remote servers. diff --git a/man/close_connection.Rd b/man/close_connection.Rd deleted file mode 100644 index 594876c..0000000 --- a/man/close_connection.Rd +++ /dev/null @@ -1,17 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/utils.R -\name{close_connection} -\alias{close_connection} -\title{Close a WebSocket connection} -\usage{ -close_connection(server, id) -} -\arguments{ -\item{server}{Server state environment.} - -\item{id}{Connection ID (temp_id or client_id).} -} -\description{ -Closes a WebSocket connection by ID. -} -\keyword{internal} diff --git a/man/create_document.Rd b/man/create_document.Rd index b24101e..2ab618b 100644 --- a/man/create_document.Rd +++ b/man/create_document.Rd @@ -7,7 +7,7 @@ create_document(server, doc_id = NULL) } \arguments{ -\item{server}{A sync_server object.} +\item{server}{An autosync_server object.} \item{doc_id}{Optional document ID. If NULL, generates a new ID.} } @@ -17,3 +17,10 @@ Document ID string. \description{ Creates a new empty Automerge document and registers it with the server. } +\examples{ +\dontshow{if (interactive()) withAutoprint(\{ # examplesIf} +server <- sync_server() +doc_id <- create_document(server) +server$close() +\dontshow{\}) # examplesIf} +} diff --git a/man/generate_document_id.Rd b/man/generate_document_id.Rd index 5eeb5d0..355e1ec 100644 --- a/man/generate_document_id.Rd +++ b/man/generate_document_id.Rd @@ -13,3 +13,7 @@ Character string (Base58Check encoded). Creates a new unique document ID compatible with automerge-repo. The ID is a 16-byte random value encoded with Base58Check. } +\examples{ +generate_document_id() + +} diff --git a/man/get_document.Rd b/man/get_document.Rd index c753764..1e88aed 100644 --- a/man/get_document.Rd +++ b/man/get_document.Rd @@ -7,7 +7,7 @@ get_document(server, doc_id) } \arguments{ -\item{server}{A sync_server object.} +\item{server}{An autosync_server object.} \item{doc_id}{Document ID string.} } @@ -17,3 +17,11 @@ Automerge document object, or NULL if not found. \description{ Retrieves an Automerge document by its ID. } +\examples{ +\dontshow{if (interactive()) withAutoprint(\{ # examplesIf} +server <- sync_server() +doc_id <- create_document(server) +get_document(server, doc_id) +server$close() +\dontshow{\}) # examplesIf} +} diff --git a/man/list_documents.Rd b/man/list_documents.Rd index 8c16fec..5117460 100644 --- a/man/list_documents.Rd +++ b/man/list_documents.Rd @@ -7,7 +7,7 @@ list_documents(server) } \arguments{ -\item{server}{A sync_server object.} +\item{server}{An autosync_server object.} } \value{ Character vector of document IDs. @@ -15,3 +15,11 @@ Character vector of document IDs. \description{ Returns the IDs of all documents currently loaded in the server. } +\examples{ +\dontshow{if (interactive()) withAutoprint(\{ # examplesIf} +server <- sync_server() +create_document(server) +list_documents(server) +server$close() +\dontshow{\}) # examplesIf} +} diff --git a/man/print.autosync_server.Rd b/man/print.autosync_server.Rd new file mode 100644 index 0000000..9333c9c --- /dev/null +++ b/man/print.autosync_server.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/server.R +\name{print.autosync_server} +\alias{print.autosync_server} +\title{Print method for autosync_server} +\usage{ +\method{print}{autosync_server}(x, ...) +} +\arguments{ +\item{x}{An autosync_server object.} + +\item{...}{Ignored.} +} +\value{ +Invisibly returns x. +} +\description{ +Print method for autosync_server +} +\keyword{internal} diff --git a/man/print.sync_server.Rd b/man/print.sync_server.Rd deleted file mode 100644 index 6d32e72..0000000 --- a/man/print.sync_server.Rd +++ /dev/null @@ -1,20 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/server.R -\name{print.sync_server} -\alias{print.sync_server} -\title{Print method for sync_server} -\usage{ -\method{print}{sync_server}(x, ...) -} -\arguments{ -\item{x}{A sync_server object.} - -\item{...}{Ignored.} -} -\value{ -Invisibly returns x. -} -\description{ -Print method for sync_server -} -\keyword{internal} diff --git a/man/sync_client.Rd b/man/sync_client.Rd index ceba149..f06cb07 100644 --- a/man/sync_client.Rd +++ b/man/sync_client.Rd @@ -26,18 +26,18 @@ for and send local changes for every open document. This is a cheap no-op when there are no changes.} } \value{ -An environment of class \code{"sync_client"} with reference semantics, +An environment of class \code{"autosync_client"} with reference semantics, representing the connection: \describe{ \item{\code{open_doc(doc_id, timeout)}}{Open a live document over this -connection and return a \code{sync_doc} handle for it (see below). +connection and return a \code{autosync_doc} handle for it (see below). Repeated calls for the same \code{doc_id} reuse the document already open on the connection rather than requesting it again.} \item{\code{close()}}{Disconnect and stop syncing all open documents.} \item{\code{active}}{Logical, whether the connection is active.} } -A \code{sync_doc} handle returned by \verb{$open_doc()} is itself an environment +A \code{autosync_doc} handle returned by \verb{$open_doc()} is itself an environment with: \describe{ \item{\code{doc}}{The live automerge document, kept in sync with the server.} diff --git a/man/sync_server.Rd b/man/sync_server.Rd index 70d2ac7..db37ea3 100644 --- a/man/sync_server.Rd +++ b/man/sync_server.Rd @@ -55,7 +55,7 @@ or \code{FALSE} (deny access). Called per client and per document. }} } \value{ -A sync_server object inheriting from 'nanoServer', with +An autosync_server object inheriting from 'nanoServer', with \verb{$start()} and \verb{$close()} methods. } \description{ diff --git a/tests/testthat/test-auth.R b/tests/testthat/test-auth.R index 593896e..eace4cb 100644 --- a/tests/testthat/test-auth.R +++ b/tests/testthat/test-auth.R @@ -40,7 +40,7 @@ test_that("auth_config creates valid configuration", { allowed_emails = "test@example.com" ) - expect_s3_class(cfg, "amsync_auth_config") + expect_s3_class(cfg, "autosync_auth_config") expect_equal(cfg$issuer, "https://accounts.google.com") expect_equal(cfg$client_id, "test-client-id") expect_equal(cfg$allowed_domains, "example.com") @@ -640,8 +640,7 @@ test_that("discover_jwks_uri fetches from well-known endpoint", { status = 200L ) }, - is_error_value = function(x) FALSE, - .package = "nanonext" + is_error_value = function(x) FALSE ) uri <- discover_jwks_uri("https://accounts.google.com") @@ -654,8 +653,7 @@ test_that("discover_jwks_uri errors on failed request", { data = structure(5L, class = "errorValue"), status = 0L ), - is_error_value = function(x) inherits(x, "errorValue"), - .package = "nanonext" + is_error_value = function(x) inherits(x, "errorValue") ) expect_error( @@ -670,8 +668,7 @@ test_that("discover_jwks_uri errors on non-200 status", { data = charToRaw("Not Found"), status = 404L ), - is_error_value = function(x) FALSE, - .package = "nanonext" + is_error_value = function(x) FALSE ) expect_error( @@ -686,8 +683,7 @@ test_that("discover_jwks_uri errors on missing jwks_uri", { data = charToRaw('{"issuer":"https://accounts.google.com"}'), status = 200L ), - is_error_value = function(x) FALSE, - .package = "nanonext" + is_error_value = function(x) FALSE ) expect_error( @@ -836,8 +832,7 @@ test_that("fetch_jwks parses keys and cache-control", { status = 200L, headers = list("Cache-Control" = "public, max-age=7200") ), - is_error_value = function(x) FALSE, - .package = "nanonext" + is_error_value = function(x) FALSE ) result <- fetch_jwks("https://example.com/jwks") @@ -858,8 +853,7 @@ test_that("fetch_jwks uses default TTL without cache-control", { status = 200L, headers = list() ), - is_error_value = function(x) FALSE, - .package = "nanonext" + is_error_value = function(x) FALSE ) result <- fetch_jwks("https://example.com/jwks") @@ -874,8 +868,7 @@ test_that("fetch_jwks errors on failed request", { data = structure(5L, class = "errorValue"), status = 0L ), - is_error_value = function(x) inherits(x, "errorValue"), - .package = "nanonext" + is_error_value = function(x) inherits(x, "errorValue") ) expect_error(fetch_jwks("https://example.com/jwks"), "Failed to fetch JWKS") @@ -887,8 +880,7 @@ test_that("fetch_jwks errors on non-200 status", { data = charToRaw("Server Error"), status = 500L ), - is_error_value = function(x) FALSE, - .package = "nanonext" + is_error_value = function(x) FALSE ) expect_error(fetch_jwks("https://example.com/jwks"), "Failed to fetch JWKS") @@ -901,8 +893,7 @@ test_that("fetch_jwks errors on empty keys", { status = 200L, headers = list() ), - is_error_value = function(x) FALSE, - .package = "nanonext" + is_error_value = function(x) FALSE ) expect_error(fetch_jwks("https://example.com/jwks"), "No keys found") @@ -922,8 +913,7 @@ test_that("fetch_jwks skips keys without kid", { status = 200L, headers = list() ), - is_error_value = function(x) FALSE, - .package = "nanonext" + is_error_value = function(x) FALSE ) result <- fetch_jwks("https://example.com/jwks") @@ -944,8 +934,7 @@ test_that("fetch_jwks skips unparseable keys", { status = 200L, headers = list() ), - is_error_value = function(x) FALSE, - .package = "nanonext" + is_error_value = function(x) FALSE ) result <- fetch_jwks("https://example.com/jwks") @@ -1220,9 +1209,9 @@ test_that("server allows auth with TLS configured", { ) on.exit(server$close()) - expect_s3_class(server, "sync_server") + expect_s3_class(server, "autosync_server") state <- attr(server, "sync") - expect_s3_class(state$auth, "amsync_auth_config") + expect_s3_class(state$auth, "autosync_auth_config") }) test_that("server rejects unauthenticated client when auth enabled", { diff --git a/tests/testthat/test-client.R b/tests/testthat/test-client.R index 5e608ff..ba86386 100644 --- a/tests/testthat/test-client.R +++ b/tests/testthat/test-client.R @@ -441,11 +441,11 @@ test_that("sync_client connects and open_doc receives document", { conn <- sync_client(server$url) on.exit(conn$close(), add = TRUE) - expect_s3_class(conn, "sync_client") + expect_s3_class(conn, "autosync_client") expect_true(conn$active) handle <- conn$open_doc(doc_id) - expect_s3_class(handle, "sync_doc") + expect_s3_class(handle, "autosync_doc") expect_true(handle$active) expect_equal(handle$doc_id, doc_id) expect_equal( diff --git a/tests/testthat/test-server.R b/tests/testthat/test-server.R index 85f5580..c930d67 100644 --- a/tests/testthat/test-server.R +++ b/tests/testthat/test-server.R @@ -3,7 +3,7 @@ test_that("sync_server creates valid server object", { on.exit(server$close()) state <- attr(server, "sync") - expect_s3_class(server, "sync_server") + expect_s3_class(server, "autosync_server") expect_type(state$peer_id, "character") expect_type(state$storage_id, "character") expect_true(is.environment(state$documents)) @@ -80,7 +80,7 @@ test_that("list_documents returns all documents", { expect_true(id2 %in% docs) }) -test_that("print.sync_server works", { +test_that("print.autosync_server works", { server <- sync_server(data_dir = tempdir()) on.exit(server$close())