Migrating
0.3 to 0.4
Section titled “0.3 to 0.4”0.4 adds raw TCP and UDP servers (TCP and UDP servers), background jobs and
cron (Background jobs, Cron schedules), webhooks
(Webhooks), SSE topics and watch
(Server-sent events and watching changes), gRPC over HTTP/3 and Unix sockets
(gRPC), the S3 and SMTP clients (Object storage (S3),
Email) and the Lambda deployment (AWS Lambda).
They are new names and new build flags (-Dnet, -Djobs, -Dwebhooks,
-Ds3, -Dsmtp, -Dlambda); an app that does not use them changes only as
the table says.
| 0.3 | 0.4 |
|---|---|
An exhaustive switch on anole.db.Class |
add the cases .not_found, .precondition and .invalid |
An exhaustive switch on anole.db.System or anole.resilience.Kind |
add the cases .s3 and .smtp |
An exhaustive switch on anole.ServerError |
add the cases NetNotBuilt, NetListenerNeedsPort, NetListenerHttp3, TcpAndUdp, UdpWithTls, UdpOnUnixSocket, UdpWithProxyProtocol, InvalidFraming and NetHandlerContext |
A restart (process) with up to 255 HTTP/3 sockets |
handoff.max_listeners is 64 TCP listeners (raw TCP ones among them) and 1024 UDP sockets (HTTP/3’s and every raw UDP listener’s); no program that fit before fails |
ServerOptions.Listener built field by field |
new fields tcp and udp (null); a server that names only raw listeners opens no HTTP listener, where before run() with no http or https always opened 127.0.0.1:8080 (a server that names a listener of any kind never did) |
| A binding built against C ABI 0.9 | rebuild against the new anole.h: the ABI is 0.10 (ANOLE_ABI_COMPATIBLE asks for an exact match before 1.0); Kafka groups and batches are handle kinds 17 and 18, so a NATS handle given to a Kafka call is ANOLE_E_INVALID; new status ANOLE_E_SERVICE (-13); docs/c-abi.md, “From ABI 0.9” |
The C ABI’s anole_stats of 0.9 |
six more u64 fields at the end (net_connections … udp_datagrams_dropped); a caller passing its size gets the fields it knows |
anole.http.Client over HTTPS offered only HTTP/1.1 |
it offers h2 too (ClientOptions.http2 is on by default) and shares one connection per origin and executor; .http2 = false keeps 0.3’s behaviour |
channel.conn (read) |
channel.conn.? (the field is optional; h3 is the other connection) |
| A gRPC client call whose stream the server reset was UNAVAILABLE | the status follows the reset code (INTERNAL for most; only a refused stream is UNAVAILABLE, so Channel.retry no longer repeats the others) |
Response.abort() in a gRPC handler answered UNKNOWN |
the stream is reset; the client reads CANCELLED |
error.DeploymentUnavailable for deployment = .lambda |
gone: deployment = .lambda serves AWS Lambda invocations (docs/lambda.md); an exhaustive switch on anole.ServerError drops DeploymentUnavailable and adds LambdaRuntimeApiMissing, LambdaNotBuilt, LambdaRuntimeApiFailed and SnapStartUnsupported |
A C consumer’s switch over every event type |
add ANOLE_EV_SSE_SUBSCRIBE, ANOLE_EV_SSE_CLOSE, ANOLE_EV_WATCH_EVENT, ANOLE_EV_WATCH_STATUS and ANOLE_EV_WATCH_END (a consumer without sse routes or watches never gets them); the new handle kinds are anole_sse and anole_watch_sub |
A Response held open by res.adopt for anything but a streamed 200 |
the same: adopt serves a streamed 200 (an event stream); anole.sse and app.sse park such a response without holding a task |
| A long-lived HTTP/3 response (a gRPC stream, SSE, WebSocket or WebTransport over HTTP/3) ended at the 30 s idle timeout when nothing moved | QUIC PINGs keep the connection open while a request is open; idle connections still close at the timeout |
| A handler on HTTP/3 ran to the end after the client’s RESET_STREAM | its task is canceled, as on HTTP/2 (the handler’s next wait returns error.Canceled) |
RequestId replaced an ID the engine had already set |
it keeps it (under Lambda, the invocation’s request ID) |
| The startup banner drew the lizard | it draws the word “anole”; the rules for when it shows, banner = false and ANOLE_NO_BANNER=1 are unchanged |
addApp(b, .{ .profile = .web }) with no way to change one feature |
.features = .{ .postgres = false } turns single features on or off over the preset |
0.2 to 0.3
Section titled “0.2 to 0.3”0.3 adds settings, .env files, deployment modes, retries and circuit
breakers, the NATS and Kafka clients, validation and typed handlers, JSON
Lines logs, OpenAPI documents and a live console. Nothing you set is changed; the
table lists what reads or behaves differently.
| 0.2 | 0.3 |
|---|---|
opts.http.?.address (read) |
opts.http.?.address orelse "127.0.0.1" (null is the deployment’s default) |
listener.port (read) |
listener.port orelse 8080 |
.proxy = .{ .trusted_hops = 1 } |
unchanged and still works; .deployment = .behind_proxy (or .proxy = .{ .trusted = &.{"private"} }) is the recommended form: it ignores headers from peers that are not your proxy |
"proxy_protocol": false written in JSON to mean “default” |
unchanged; leaving it out now means “the deployment’s default” (false unless anole_lb) |
X-Forwarded-For "1.2.3.4:5678" gave 0.0.0.0 |
gives 1.2.3.4 |
A Debug build read no .env |
a Debug build is development and reads .env files from the working directory; ANOLE_ENV=production or .env_files = &.{} turns that off |
A Debug build’s 500s said Internal Server Error |
they name the error (error_detail, development); .error_detail = false keeps 0.2’s |
timeouts.keepalive 60 s |
75 s (set .timeouts = .{ .keepalive = .fromSeconds(60) } for the old value) |
| WebSockets over HTTP/2 (RFC 8441) accepted by default: Firefox opened them on the h2 connection | off: the HTTP/2 engine no longer advertises extended CONNECT, so clients open WebSockets over HTTP/1.1 (38% more video-call users, half the CPU per message, #91); .http2 = .{ .enable_connect_protocol = true } turns it back on. RFC 9220 over HTTP/3 is unchanged |
| PostgreSQL, Redis and MongoDB calls tried to connect every time while the database was down | a circuit breaker is on by default: after 5 connection failures in a row calls fail at once with 503 (lastError code circuit_open); .breaker = null ("breaker": null in the C ABI) keeps 0.2’s behaviour |
An exhaustive switch on anole.db.System |
add the cases .nats and .kafka |
req.bind(T) on a body that is JSON but does not fit: 400 {"error":"Bad Request"} |
422 application/problem+json with errors naming each field; not JSON or no body stays 400. req.bindWith(T, .{ .ignore_unknown_fields = true }) is the old behaviour |
app.get(path, ...) with a run-time path |
the route methods take comptime path; use app.addRoute(.get, path, handler) |
A pattern with a wildcard before the end, a parameter named twice, an empty parameter name, or ?/# |
refused: a compile error, or a startup conflict (ANOLE_E_CONFIG through the C ABI) |
anole.cached on a POST route |
a compile error (the cache keeps GET and HEAD answers only) |
Access log JSON: {"ts":1792333306429,"method":...} |
the shared record: {"time":"2026-10-23T09:41:07.415Z","level":"info","scope":"access","message":"GET /users/7 200",...,"method":...}; AccessLog.init(.{}) follows the log format (JSON in production), .format = .text keeps text |
| A route conflict in a test build crashed the test binary | run, listen and anole.testing.client fail with error.RouteConflict; app.routeConflict() has the message |
C ABI: the log callback’s msg was the message |
with log.format "json" (the default when environment is production, which is what an unset environment gets) it is a whole JSON record; "log": {"format": "text"} keeps messages. Access log lines now reach the callback (scope access); "access_log".format defaults to "auto" |
C ABI JSON config (ABI 0.9)
Section titled “C ABI JSON config (ABI 0.9)”The C ABI moves to 0.9; rebuild against the new anole.h (ANOLE_ABI_COMPATIBLE asks for an exact match before 1.0).
| 0.8 | 0.9 |
|---|---|
listeners[].address, port, proxy_protocol defaulted to "127.0.0.1", 8080, false |
default null: the deployment’s value (the same three under standalone) |
timeouts.keepalive_ms 60000 |
75000 |
http2.enable_connect_protocol true |
false ("http2": {"enable_connect_protocol": true} for 0.8’s behaviour) |
proxy_protocol_from empty: any peer |
empty: proxy.trusted when set, else any peer |
the log callback’s msg was the message |
a whole JSON record when log.format resolves to "json" (an unset environment is production); "log": {"format": "text"} keeps messages; access lines reach the callback (scope access) |
| a malformed route pattern was accepted | ANOLE_E_CONFIG naming the route |
| PostgreSQL, Redis and MongoDB entries tried to connect on every call while the database was down | a breaker is on by default (503, error_code circuit_open); "breaker": null keeps 0.8’s behaviour |
| (none) | environment, deployment, error_detail, proxy.trusted, log (format, service), banner, console, brokers (nats, kafka); databases[].breaker, retry; anole_dotenv_read, anole_nats_*, anole_kafka_*, anole_console_*, ANOLE_E_BROKER, ANOLE_DB_F_RETRY |
0.1 to 0.2
Section titled “0.1 to 0.2”0.2 batches the breaking changes of the API review. Each area has its section below; the changelog lists the rest. Before 1.0 there is no stability promise, but every change here is a rename or a move, not a redesign, and the compiler names each one.
Server options, TLS and errors
Section titled “Server options, TLS and errors”ServerOptions
Section titled “ServerOptions”| 0.1 | 0.2 |
|---|---|
.server = .{ .timeout = .{ .request = d } } |
.timeouts = .{ .request = d } |
.server.timeout.read_header |
.timeouts.read_header |
| (none) | .timeouts.write (30 s, new) |
.server.timeout.keepalive |
.timeouts.keepalive |
.server.timeout.idle_park |
.timeouts.idle_park |
.server.timeout.shutdown (30 s) |
.timeouts.shutdown (10 s) |
.server.timeout.request_count |
.limits.max_requests_per_connection |
.server.request.max_body_size |
.limits.body_max |
.server.request.max_content_length |
.limits.body_max (one limit for both) |
.server.request.buffer_size |
.limits.max_head_size |
.server.request.max_header_count |
.limits.max_header_count |
.server.request.max_param_count / max_query_count / max_form_count / max_multiform_count |
.limits.<same> |
.server.request.require_host |
.request.require_host |
.server.request.decompress |
.request.decompress |
.server.request.keep_buffers_between_requests |
.request.keep_buffers |
.server.response.* |
.response.* (same names) |
.server.max_connections (null: no cap) |
.limits.max_connections (0: the open-file limit; no “no cap”) |
.server.trusted_proxy_hops |
.proxy.trusted_hops |
.server.trust_forwarded_proto / trust_forwarded_host |
.proxy.trust_forwarded_proto / trust_forwarded_host |
.server.errors, .server.error_handler, .server.http2, .server.listeners (ignored before) |
.errors, .error_handler, .http2, .listeners |
.restart_timeout_ms = 10_000 |
.restart_timeout = .fromSeconds(10) |
.client = http.ClientConfig{...} |
.client = anole.ClientOptions{...} (same fields; tls is TlsOptions) |
.https.alpn_protocols, Listener.Tls.alpn_protocols |
.alpn |
.client_auth = .{ .ca = .{ .file = .{ .path = "ca.pem" } } } |
.client_auth = .{ .ca = .fromFile("ca.pem") } |
anole.http.ServerConfig.Tls.ClientAuth |
anole.ClientAuth |
anole.http.TlsCa |
anole.Ca (.file/.dir take a path string; no dir handle) |
TlsConfig.ClientCertificate{ .cert_path, .key_path } |
anole.Cert.fromFiles(cert, key) (or .fromPem) |
http.ClientConfig, http.TlsConfig |
anole.ClientOptions, http.TlsOptions |
A file-based CA or certificate used to be resolved against a dir handle;
paths are now relative to the working directory, so a test that wrote its
certificates into a temporary directory passes the path of the files there.
A .dev_self_signed client certificate is not supported by the client types
(error.DevCertificateUnavailable): make one with anole.tls.selfSigned and
pass it with .fromPem.
Requests and responses
Section titled “Requests and responses”| 0.1 | 0.2 |
|---|---|
req.setTimeout(.{ .duration = .{ .raw = .fromSeconds(60), .clock = .awake } }) |
req.setTimeout(.fromSeconds(60)) |
req.setTimeout(.none) |
req.setTimeout(null) (rarely needed: streams lift the deadline themselves) |
req.setTimeout(.{ .deadline = t }) |
req.setDeadline(.{ .deadline = t }) |
res.writer() |
try res.writer() |
res.headers_written |
res.headersSent() |
res.conn, req.parser, req.transport, … |
internal (_-prefixed); not for handlers |
Response.init, Request.reset |
Response._init, Request._reset (engines only) |
chunked req.reader() unbounded |
bounded by limits.body_max; req.readerLimited(buf, n) for more |
Middleware and helpers
Section titled “Middleware and helpers”| 0.1 | 0.2 |
|---|---|
fn execute(self, req, res, executor: anytype) !void { return executor.next(); } |
fn execute(self, req, res, next: anytype) !void { return next.call(); } |
Compress.init() |
Compress.init(.{}) |
Logger.init(), Logger.initWith(.{ ... }) |
Logger.init(.{ ... }) |
Cors.Config, RequestId.Config, Logger.Config, session.Config, csrf.Config |
...Options |
session.ConfigError |
session.OptionsError |
MemoryStore.init(gpa, io), initOptions(gpa, io, o) |
MemoryStore.init(gpa, io, .{ ... }) |
session ttl_seconds |
ttl (Duration) |
ratelimit window_ms, idle_ms |
window, idle |
auth *_seconds (leeway_seconds, ttl_seconds, max_age_seconds, …) |
same names without _seconds (Duration) |
Cors max_age = 600 |
max_age = .fromSeconds(600) |
TracingOptions.interval_ms |
interval |
tls.SelfSignedOptions.valid_for_seconds |
valid_for |
MemoryLimiter + ratelimit.Middleware.init(...) |
still works; or app.rateLimit(.{ .limit = 100 }) |
Errors
Section titled “Errors”| 0.1 | 0.2 |
|---|---|
init/listen/run return inferred anyerror sets |
anole.ServerError (named; switch on it) |
reloadCertificates/setCertificate |
anole.TlsLoadError |
| out-of-order request/response use: undefined behaviour in ReleaseFast | anole.http.MisuseError, answered 500 |
Behaviour you may notice
Section titled “Behaviour you may notice”- The drain waits 10 s (was 30 s). Docker’s default stop grace is also 10 s:
give containers
stop_grace_period: 15s. - Event streams, streamed responses and downloads are no longer cut at 30 s;
a client that stops reading is cut after
timeouts.write(30 s). - WebSocket messages over 1 MiB are refused by default (was 16 MiB).
listen("3000")works (every interface).- Setting one
http2field keeps 250 streams. app.tracing()honoursOTEL_*without.process.- Outbound requests say
User-Agent: anole/<version>.
C ABI JSON config (ABI 0.7)
Section titled “C ABI JSON config (ABI 0.7)”| 0.6 | 0.7 |
|---|---|
trusted_proxy_hops |
proxy.trusted_hops |
trust_forwarded_proto, trust_forwarded_host |
proxy.trust_forwarded_proto, proxy.trust_forwarded_host |
timeouts.*_ms: integers |
integers or null (none) |
middleware[].jwt.leeway_s |
leeway_ms |
middleware[].cors.max_age |
max_age_ms |
| (none) | timeouts.read_header_ms, write_ms, idle_park_ms; limits.max_head_size, max_header_count, max_param_count, max_query_count, max_form_count, max_multiform_count, max_requests_per_connection; proxy, request, response, http2, errors; http3.address, port, validate_address, prefer_v2, sockets, enable_connect_protocol, inline_no_wait; tls.client_ca_dir, client_ca_system |
HTTP, WebSocket and WebTransport
Section titled “HTTP, WebSocket and WebTransport”Middleware and noWait
Section titled “Middleware and noWait”A route marked noWait now takes only middleware that says it never waits.
Each startup panic names the route and the middleware:
- remove
noWaitfrom the route; or - add
pub const no_wait = true;to a struct middleware of yours that truly never waits (orpub fn noWait(self: *const T) boolwhen it depends on its settings); a function middleware is registeredapp.use(anole.noWait(f)).
The built-in middleware is marked (see docs/routing.md). Interfaces gained a
flag your own implementations may set: ratelimit.Limiter.never_waits,
auth.apikey.Lookup.never_waits, session.Store.never_waits (the memory
implementations set it; a Redis or database one leaves it off, and a
noWait route behind that middleware is refused).
WebSocket and WebTransport
Section titled “WebSocket and WebTransport”| 0.1 | 0.2 |
|---|---|
ws.conn == .h2, ws.conn.h1 |
ws.httpVersion() != .http1 (.http1, .http2, .http3) |
ws.closed.load(.acquire) |
ws.isClosed() |
Rooms.init(a), Rooms.initOptions(a, o) |
Rooms.init(a, .{}), Rooms.init(a, o) |
res.upgradeWebSocket(req) |
anole.websocket.upgrade(req, res, .{}) (all three HTTP versions) |
session.start(...), tryAccept, tryOpen, takeDatagram, freeDatagram, trySendDatagram, generation, waitChange, wake |
Session.Driver.<same>(session, ...) (Anole’s own drivers; not for handlers) |
stream.tryRead, tryWrite, sendError, Stream.Poll |
Stream.Driver.<same>, Stream.Driver.Poll |
req.params.get("name") for an encoded segment |
still raw; try req.paramDecoded("name") decodes |
New and additive: UpgradeOptions.subprotocols and .headers,
websocket.isUpgrade(req), ws.subprotocol, ws.httpVersion(),
ws.isClosed().
C ABI 0.7: ANOLE_EV_WS_UPGRADE and anole_ws_accept are opt-in
("accept": "consumer" on a websocket route); an existing config sees
nothing new except the subprotocol span in request records (empty outside
ANOLE_EV_WS_OPEN). Publishes to one topic are now ordered.
The C ABI (0.6 to 0.7)
Section titled “The C ABI (0.6 to 0.7)”The C ABI moves to version 0.7. Before 1.0 every minor may break, so a library
serves only code built against the same version: check
ANOLE_ABI_COMPATIBLE(anole_abi_version()) at load, and replace any
major == / minor >= comparison with it.
Rename the calls and constants (a search and replace is enough for most):
| 0.6 | 0.7 |
|---|---|
anole_respond(r, ...) |
anole_req_respond(r, ...) |
anole_respond_fd(r, ..., int fd, ...) |
anole_req_respond_fd(r, ..., intptr_t fd, ...) |
anole_stream_start/write/write_wait/end |
anole_req_stream_start/write/write_wait/end |
anole_release(r) |
anole_req_fail(r, 0, (anole_str){0}) (500) or anole_handle_release(r) (finalizers) |
| (none) | anole_req_abort(r) |
anole_wt_stream_release(s) |
anole_handle_release(s) |
anole_server_port(s, i, &port) |
anole_server_listener(s, i, &info); info.port, info.udp_port |
anole_server_reload_certificates(s) |
anole_server_reload_tls(s, 0, (anole_str){0}, (anole_str){0}) |
anole_server_reload_tls(s, f, c, cl, k, kl) |
anole_server_reload_tls(s, f, (anole_str){c, cl}, (anole_str){k, kl}) |
anole_wt_send_datagram(w, d, n) |
anole_wt_send_datagram(w, d, n, 0) |
ANOLE_EV_WS_DRAIN |
ANOLE_EV_WRITABLE (ev.handle the socket) |
ANOLE_EV_ABORT read as anole_event |
anole_abort_event (reason, status) |
anole_request_spans(r) = r + 1 |
the same helper, now reading r->spans_off |
anole_request_peer_cert(r) |
anole_event_str(r, r->peer_cert) |
gRPC metadata at c + 1 |
anole_grpc_metadata(c) (c->metadata_off) |
ANOLE_DB_INT… |
ANOLE_DB_TYPE_INT…; ANOLE_DB_WRITE… are ANOLE_DB_F_* |
ANOLE_GRPC_OK… |
ANOLE_GRPC_STATUS_OK…; ANOLE_GRPC_UNARY… are ANOLE_GRPC_KIND_*; ANOLE_GRPC_SEND_FINISH is ANOLE_GRPC_F_FINISH |
ANOLE_WT_UNI, ANOLE_WT_FIN |
ANOLE_WT_F_UNI, ANOLE_WT_F_FIN (values kept) |
ANOLE_TLS_PEM, ANOLE_SERVER_SIGNALS, ANOLE_CONSUMER_SHARED, ANOLE_POLL_NOARM |
ANOLE_TLS_F_PEM, ANOLE_SERVER_F_SIGNALS, ANOLE_CONSUMER_F_SHARED, ANOLE_POLL_F_NOARM |
enum-valued parameters as int (anole_ws_send’s type, the log callback’s level) |
uint32_t; anole_consumer_fd returns intptr_t; anole_has_feature returns int32_t |
Behaviour that changes with the rename:
- Cleanup calls are idempotent.
anole_handle_release,anole_req_abort,anole_ws_close,anole_ws_unsubscribe,anole_wt_close,anole_wt_stream_resetandanole_wt_stream_stopanswerANOLE_OKon an object that is already over, set no error and are not counted inanole_stats.gone. Code that treatedANOLE_E_GONEfrom a close or release as harmless keeps working (it now gets OK). Answer and data calls still answerANOLE_E_GONE. - A handle of the wrong kind, or 0, is
ANOLE_E_INVALID(tokenWrongHandleKind), notANOLE_E_GONE. Code that relied on GONE to detect a wrong-kind handle must checkANOLE_E_INVALID. - Options structs and flags are checked. An options struct larger than the
library knows is refused unless the extra bytes are 0; unknown flag bits
(
anole_db_options.flagsincluded) and non-zero reserved fields (anole_db_value.reserved,anole_db_options.reserved) areANOLE_E_INVALID. ANOLE_E_TOOBIGfrom a poll writes anANOLE_EV_NONEheader with the neededlen; allocate that once instead of doubling.- Records: read the header spans through
spans_offand the gRPC metadata throughmetadata_off, neversizeof; the client certificate is a span (peer_cert), no longer a length-prefixed blob. anole_ws_publishon a server that is not running isANOLE_E_STATE(wasANOLE_E_GONE); a faileddupinanole_req_respond_fdisANOLE_E_SYSTEM(wasANOLE_E_INVALID).- Generate, do not hand-write:
include/anole.abi.json(andinclude/anole.schema.jsonfor the configuration) describe every record, constant, event and handle kind; generate your parsers from them, pinned to the Anole tag.
New in 0.7 (see docs/c-abi.md): anole_server_open, anole_server_set_ready,
anole_server_listener(_count), anole_last_error_name, anole_last_errno,
anole_consumer_set_wake, the allocation table, abort reasons, and a destroy
that does not wait for answers only its caller could give.
Databases and gRPC
Section titled “Databases and gRPC”Options (every connector)
Section titled “Options (every connector)”- Every call is bounded by 30 s.
Options.timeoutexists on all four connectors (it was MongoDB’s alone, and off) and defaults to 30 s;TxOptions.timeout = nullnow means the connector’s too. A call that must run longer (a migration, a background job) passes.with(.{ .timeout = .max }), or settimeout = nullon the connector. C ABI:anole_db_options.timeout_ms = 0now means the database’stimeout_ms(30000), not “none”; useANOLE_DB_TIMEOUT_NONE. - One vocabulary, renamed fields:
| 0.1 | 0.2 |
|---|---|
sqlite.Options.busy_timeout_ms = 5000 |
busy_timeout = .fromSeconds(5) |
sqlite.Options.statement_cache = 64 |
statement_cache_size = 64 (u16) |
sqlite.Options.readers: ?usize |
?u32 |
mongodb.Options.max_idle |
idle_timeout |
mongodb.Options.max_connections: usize = 100 (0: no limit) |
?u32, null for 100; 0 is refused |
mongodb.Options.connect_timeout: Duration |
?Duration (null: none) |
mongodb.Options.tls: ?net.ConnectOptions.Tls |
tls: anole.db.Tls (ca, client_certificate, insecure_skip_verify) |
pg.Options.tls, redis.Options.tls |
the same anole.db.Tls (old paths still compile) |
.tls = .{ .ca = .{ .file = .{ .path = "ca.pem" } } } |
.tls = .{ .ca = .{ .file = "ca.pem" } } (anole.Ca) |
name: []const u8 = "postgres" |
name: ?[]const u8 = null (the same default label) |
sqlite.Client.execScript(sql) |
execScript(&ctx, sql); pg.Client.execScript(&ctx, sql) is new (both unbounded) |
pg/sqlite Query.run(req, sql, values), Tx.run |
execValues (table keeps its name) |
anole.mongodb.dynamic.run(collection, database, ctx, op, docs) |
still there; app.mongodb.with(opts).dynamic(ctx, op, "users", docs) takes the context first |
max_connections = 0 |
refused at init with error.InvalidOptions on PostgreSQL, Redis and MongoDB |
New fields: connect_at_start and ping_timeout on MongoDB,
ping_timeout on SQLite. Conn.release takes *const Conn on every
connector (so const conn = try app.redis.acquire(req); defer conn.release(); compiles).
- Leaving a transaction open is caught on more paths.
iteratethat leaves one, and a connection fromacquiregiven back inside one, are rolled back and logged, as plain calls already were.Tx.table,Tx.execValuesandTx.with(options)exist on both SQL connectors; a finished SQLiteTxfails witherror.Databaseinstead of asserting. - Role switches inside functions under
.auto. PostgreSQL does not reportrole, soreset_session = .autodoes not catchSET ROLEmade inside a function; use.alwaysfor apps that switch roles for row-level security, orSET LOCAL ROLEinside a transaction.
C ABI: databases
Section titled “C ABI: databases”anole_db_optionsis 48 bytes (reqat the end) andanole_db_result_event104 (tablespan at the end); read both throughsize/lenas before.- New:
anole_db_begin,anole_db_tx_query,_exec,_commit,_rollback,_release(handle kinddb_tx),ANOLE_DB_TIMEOUT_NONE,ANOLE_DB_TX_READ_ONLY,ANOLE_DB_ISOLATION_*;anole_db_queryandanole_db_execaccept a NULL consumer withanole_db_options.req. - JSON config:
databases[].timeout_ms(30000),errors, client certificates (client_cert_file,client_key_file,client_cert_pem,client_key_pem),insecure_skip_verifyandping_timeout_msfor PostgreSQL, Redis and MongoDB; MongoDBidle_timeout_ms,heartbeat_ms,retry_reads,retry_writes,connect_at_start; SQLiteuri,ping_timeout_ms.sqlite.statement_cacheisstatement_cache_size, andmongodb.max_connections0 is refused. - A libanole built with Redis or MongoDB but neither SQLite nor PostgreSQL
answers
anole_db_query(it used to refuse every call).
Call.addHeaderandCall.addTrailerrefuse the protocols’ own keys (grpc-*butgrpc-status-details-binin trailers,connect-*,trailer-*,content-type,te…): a handler could send a secondgrpc-status.addHeaderadds a value instead of replacing one. The C ABI also refusesgrpc-status-details-binas initial metadata.Call.fail,addTrailerandaddHeadercopy their strings;Call.failFmt(code, fmt, args)is new.Group.grpcandGroup.grpcWithregister a service behind a group’s middleware, for its gRPC calls and its transcoded routes alike (before, a transcoded/v1/...route skipped a/v1group’s auth middleware). C ABI:grpc.services[].middleware.