Changelog
Copy Markdown[v0.10.0] - 2026-03-12
Added
:settingsoption forURP.convert/2: pass{path, property, value}triplets to configure soffice viaConfigurationUpdateAccessbefore each conversion. Settings sharing the same nodepath are batched into a single update call. Useful for tuning cache limits, graphic memory, etc.:iooption forURP.convert/2: choose between file-based (:file, default) and streaming (:stream) I/O independently for input and output. File I/O uses temp files on soffice's filesystem (~6 round-trips). Streaming uses XInputStream/XOutputStream over URP (~40-50% slower but no temp disk). Mixed modes supported:io: {:file, :stream}orio: {:stream, :file}.:recv_timeoutand:max_frame_sizeoptions forURP.convert/2: per-conversion control over the TCP recv timeout (default 120 s) and maximum accepted frame size (default 512 MiB).- Infinite reconnection with backoff: the pool retries soffice connections
forever with exponential backoff (
backoff_initial/backoff_max) instead of crashing. Emits[:urp, :connection, :retry]telemetry on each attempt. - Frame size guard:
recv_framerejects frames larger than 512 MiB (configurable) before allocating, preventing OOM from corrupt wire data.
Fixed
- Pool query checkin leak: diagnostic queries (
version,services, etc.) did not resetconn.private,conn.reply, orconn.doc_oidon checkin, leaking state across pool reuses. All pool operations now go throughreset_conversion_state/1. - Default pool ignored backoff config:
backoff_initialandbackoff_maxfromconfig :urp, :defaultwere silently dropped. Application now forwards the full config map to the pool. - Large file conversion (>64 MB output):
gen_tcp.recvreturns:enomemfor single reads above ~64 MB.recv_framenow reads in 4 MB chunks and reassembles. Also eliminates redundant binary copies insend_frame(iodata passthrough) andwrite_bytes(enc_str_iodata/1). - Stale
conn.replyleaking as conversion result:conn.replyretained values from bootstrap or diagnostic queries. Early conversion failures (e.g. file not found) could return stale data as a successful result. Convert now clearsconn.replyon checkout with strict type-checked result matching. - TID cache loss across conversions: the URP type cache accumulated during each conversion is now persisted back to the connection struct, preventing type desync on subsequent operations.
- O(n²)
fill_bufferin enum streams: chunk accumulation now uses iodata instead of repeated binary concatenation, flattening once when consumed.
Changed
- Iodata frame builders:
CallandProtocolreturn iodata instead of flattened binaries. The singleIO.iodata_to_binarycall happens in Protocolsend_frame/2, eliminating redundant intermediate allocations. - Bridge error paths use
reply: nilinstead ofreply: ""for consistency.
[v0.9.1] - 2026-03-06
- Fix 2 GiB memory spike during conversion:
read_file/2now callsavailable()to get the exact output size beforereadBytes(). Previously it passed0x7FFFFFFF(2 GiB), causing soffice to pre-allocate a 2 GiB buffer on every conversion regardless of actual PDF size. Peak soffice RSS drops from ~2.3 GB to ~300 MB.
[v0.9.0] - 2026-03-04
- Add
:telemetryevents — every pool operation emits[:urp, :call, :stop]withqueue_time,service_time, andtotal_timemeasurements. SeeURP.Telemetryfor details.
[v0.8.1] - 2026-03-03
Internal refactor of Bridge and Pool internals. No public API changes.
- Plug-like error handling: Bridge functions no longer raise — errors
accumulate on
conn.errorand short-circuit subsequent calls via guards. This replaces 13+ rescue blocks with a single, predictable data-flow pattern. Callers piping through Bridge get clean error propagation without try/rescue. - Uniform return type: All Bridge functions return
t(). Functions that previously returned tuples (store_document_write,store_to_stream,read_file) now stash results inconn.reply, enabling consistent piping. - Pool memory fix:
reset_conversion_statenow clearsconn.reply, preventing large binaries from being held between conversions. - Accurate type specs: Bridge struct fields (
sock,desktop_oid,ctx_oid,smgr_oid) now correctly typed as| nil;replyisterm(). - CI: Debian soffice 26.2 with Docker layer caching — replaces Alpine image, enables LO 26.2+ tests (markdown export).
- Expanded test coverage: bang variants, Bridge-level
store_to_stream, temp file cleanup.
[v0.8.0] - 2026-03-02
- Add
URP.services/1— list all registered UNO service names - Add
URP.filters/1— list all export filter names (discover available filters at runtime) - Add
URP.types/1— list all document type names - Add
URP.locale/1— query soffice locale setting - Add Protocol
parse_string_sequence_reply/1forsequence<string>replies - Add Diagnostics section to module docs with examples
[v0.7.0] - 2026-03-02
- File-based I/O: load and store documents via XSimpleFileAccess instead of XInputStream/XOutputStream streaming — eliminates thousands of TCP round-trips per conversion, ~8x faster for typical documents
- Breaking:
close_document!/2now returnst()instead of{binary(), t()} - Pipeline-friendly Bridge helpers — all conn-threading helpers (
call,sfa_call,qi) return justconn; enablesconn |> qi(...) |> call(...)style - Add
last_replyandlast_errorfields to Bridge conn for introspection/debugging - Add
:filter_dataoption for export-specific settings (e.g.[UseLosslessCompression: true, ExportFormFields: false]for PDF) - Add XSeekable support — ZIP-based formats (docx, xlsx, pptx, odt) stream without buffering the entire file first
- Reuse connections across conversions (no reconnect per document)
- Pre-compute static URP frame bodies at compile time
- Fix TID cache for cross-thread XSeekable replies
- Replace inline magic numbers with named constants throughout
- Add PERFORMANCE.md with benchmarks and container recommendations
- Add Benchee benchmark suite
- Switch Docker images to libreoffice-*-nogui packages
[v0.6.1] - 2026-03-01
- Add
URP.version/1— query soffice version string over URP (no CLI access needed) - Hide URP.Pool from hex docs (internal module)
- Simplify README
[v0.6.0] - 2026-03-01
- Breaking:
:filteroption is now required — no default export filter - Remove PDF-centric language from docs — URP is a generic document conversion tool
[v0.5.0] - 2026-03-01
- Breaking: unified API — single
URP.convert/2replaces convert_stream/2, convert_file_stream/2, and convert/3 - Breaking: remove URP.convert_stream/2, URP.convert_file_stream/2, URP.convert/3, URP.Pool.convert_stream/3, URP.Pool.convert_file_stream/3, URP.Pool.convert_url/4
- Breaking: output destination is now
:outputoption (path,:binary, orfun/1) instead of:sink - Breaking: default output writes to temp file (returns
{:ok, path}) instead of accumulating bytes in memory - Add enumerable input support — pass any
Enumerable(e.g.File.stream!/2, S3 download streams) toURP.convert/2 - Add lazy enumerable streaming (enum reader + Bridge stream loader)
[v0.4.0] - 2026-02-28
- Breaking: remove
use URP, otp_app: :my_appmacro — call URP.convert_stream/2, URP.convert_file_stream/2, URP.convert/3 directly - Breaking:
URP.Test.stub/1replaces URP.Test.stub/2 — stubs are global, no module name needed - Breaking: remove URP.Test.start/0 — ownership server starts automatically
- Add URP.Application — default pool, DynamicSupervisor, and ownership server start automatically
- Add named pools via
config :urp, :pools— started on first use via DynamicSupervisor - Handle soffice DisposedException with reactive retry on reconnect (matches C++ callers' approach)
- Make nimble_ownership a required dependency (no longer optional/test-only)
[v0.3.1] - 2026-02-26
- Exclude Mix.Tasks.Bump from hex package to avoid module conflicts
[v0.3.0] - 2026-02-26
- Fix protocol correctness: validate block count, support FUNCTIONID16/14, skip MOREFLAGS byte
- Fix one-way detection: use func_id (only
releaseis one-way), not unreliable header flags - Add
parse_exception/1— extract human-readable messages from UNO exception replies - Include exception details in error messages from
load_document!andstore_to_url! - Add protocol unit tests (28 tests covering header parsing, encoding, reply classification)
- Add error handling integration tests
- Simplify mix bump task (remove network dependencies, fix editor hang with gpg signing)
[v0.2.0] - 2026-02-26
- Breaking: remove URP.Connection — all calls go through URP.Pool via wrapper modules
- Make
use URProute all calls through a supervised Pool - Change default pool_size from 4 to 1 (matches single soffice instance)
- Rewrite README: clarify setup steps, explain function differences, add design tradeoffs
- Add Kubernetes scaling note
- Fail hard when soffice is not reachable in tests (instead of silently skipping)
- Hide mix bump from hexdocs
[v0.1.2] - 2026-02-26
- Add otp_app config support for URP.Pool
- Add soffice service to CI for integration tests
- Add
:mixto dialyzer PLT apps - Add release workflow and mix bump task
- Remove stale urp_convert.exs script
- Fix license year
[v0.1.1] - 2026-02-25
- Add streaming conversion via XInputStream/XOutputStream (no shared filesystem needed)
- Add file-backed streaming (convert_file_stream/2)
- Add sink option for streaming output to file or callback
- Add URP.Pool (NimblePool) for connection pooling
- Add
use URPmacro andURP.Testfor stubbable wrapper modules - Add typespecs to all public functions
- Add Nix flake dev shell
- Add dialyzer
- Make README the main page on hexdocs
[v0.1.0] - 2026-02-25
- Initial release