Repository navigation
Migrating
0.4 adds raw TCP and UDP servers (Net), background jobs and
cron (Jobs, Cron), webhooks
(Webhooks), SSE topics and watch
(Realtime), gRPC over HTTP/3 and Unix sockets
(gRPC), the S3 and SMTP clients (Storage,
Email) and the Lambda deployment (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.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"
|
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.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.
| 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.
| 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 |
| 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 })
|
| 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 |
- 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>.
| 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
|
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).
| 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 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.
-
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.
-
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.