Changelog
Edit this page on GitHubNote: The changelog for the older version can be found here: Changelog Archive
Version 2.29.0 (2025-07-08)
Core Library
Info Events Streaming Feature
This feature enables sending info events raised with raise info statements as SSE (Server-Side Events). When enabled, endpoints can stream real-time notifications to connected clients.
How It Works
When this feature is enabled, an endpoint will have an additional URL that can be used to connect as a new EventSource
which will receive RAISE INFO
notifications:
const source = new EventSource(url);
source.onmessage = event => {
console.log(event.data);
};
const source = new EventSource(url);
source.onmessage = event => {
console.log(event.data);
};
Generated Client Code
This functionality is encapsulated into the generated TsClient (TypeScript and JavaScript code) as a function parameter that accepts a string:
const createRaiseNoticesEventSource = (id = "") => new EventSource(baseUrl + "/api/raise-notices/info?" + id);
async function raiseNotices(
info = msg => msg
) {
const executionId = window.crypto.randomUUID();
const eventSource = createRaiseNoticesEventSource(executionId);
eventSource.onmessage = event => {
info(event.data);
};
try {
const response = await fetch(raiseNoticesUrl(), {
method: "GET",
headers: {
"X-NpgsqlRest-ID": executionId
},
});
return response.status;
}
finally {
setTimeout(() => eventSource.close(), 1000);
}
}
const createRaiseNoticesEventSource = (id = "") => new EventSource(baseUrl + "/api/raise-notices/info?" + id);
async function raiseNotices(
info = msg => msg
) {
const executionId = window.crypto.randomUUID();
const eventSource = createRaiseNoticesEventSource(executionId);
eventSource.onmessage = event => {
info(event.data);
};
try {
const response = await fetch(raiseNoticesUrl(), {
method: "GET",
headers: {
"X-NpgsqlRest-ID": executionId
},
});
return response.status;
}
finally {
setTimeout(() => eventSource.close(), 1000);
}
}
By default, only sessions that initiated the original code will receive these notifications by using the function parameter. This behavior is also configurable.
Configuration Properties
There are three new properties on every endpoint instance to support this feature:
public string? InfoEventsStreamingPath { get; set; } = null;
public InfoEventsScope InfoEventsScope { get; set; } = InfoEventsScope.Self;
public HashSet<string>? InfoEventsRoles { get; set; } = null;
InfoEventsStreamingPath
Additional path appended as a subpath to the main endpoint path (null disables info events). If the endpoint path is /path
and this value is set to /info
, the streaming path will be /path/info
.
InfoEventsScope
Scope that determines to whom events are streamed:
Self
(default): Only the original endpoint initiator session, regardless of the security context.Matching
: Sessions with matching security context of the endpoint initiator. If the endpoint initiator requires authorization, all authorized sessions will receive these messages. If the endpoint initiator requires authorization for certain roles, all sessions requiring the same roles will receive these messages.Authorize
: Only authorized sessions will receive these messages. If theInfoEventsRoles
property contains a list of roles, only sessions with those roles will receive messages.All
: All sessions regardless of the security context will receive these messages.
InfoEventsRoles
List (hash set) of authorized roles that will receive messages when InfoEventsScope
is set to Authorize
.
Comment Annotations
There are two new sets of comment annotations to support this feature:
info_path [ path | true | false ]
info_events_path [ path | true | false ]
info_streaming_path [ path | true | false ]
info_scope [ self | matching | authorize | all ] | [ authorize [ role1, role2, role3 [, ...] ] ]
info_events_scope [ self | matching | authorize | all ] | [ authorize [ role1, role2, role3 [, ...] ] ]
info_streaming_scope [ self | matching | authorize | all ] | [ authorize [ role1, role2, role3 [, ...] ] ]
Note: these annotations are also available as comment annotation parameters (key = value
format):
info_path = [ path | true | false ]
info_events_path = [ path | true | false ]
info_streaming_path = [ path | true | false ]
info_scope = [ self | matching | authorize | all ] | [ authorize [ role1, role2, role3 [, ...] ] ]
info_events_scope = [ self | matching | authorize | all ] | [ authorize [ role1, role2, role3 [, ...] ] ]
info_streaming_scope = [ self | matching | authorize | all ] | [ authorize [ role1, role2, role3 [, ...] ] ]
1. Set the Info Streaming Path
info_path [ path | true | false ]
info_events_path [ path | true | false ]
info_streaming_path [ path | true | false ]
Note: This can also be boolean. When set to true
, the info streaming path will be /info
which will be added to the main path.
2. Set the Info Streaming Scope
info_scope [ self | matching | authorize | all ] | [ authorize [ role1, role2, role3 [, ...] ] ]
info_events_scope [ self | matching | authorize | all ] | [ authorize [ role1, role2, role3 [, ...] ] ]
info_streaming_scope [ self | matching | authorize | all ] | [ authorize [ role1, role2, role3 [, ...] ] ]
Set the scope for sessions receiving info messages: self
(default), matching
, authorize
, or all
. When using authorize
, add an optional list of authorized roles.
Per-Message Scope Control
Scope comment annotation can be set on individual messages as the hint
parameter:
raise info 'Self messages will be received only by sessions that initiated the original endpoint. This is the default if not set otherwise.' using hint = 'self';
raise info 'Only for sessions with matching security context as the session that initiated the original endpoint.' using hint = 'matching';
raise info 'Only for authorized sessions.' using hint = 'authorize';
raise info 'Only for authorized sessions with roles role1 and role2.' using hint = 'authorize role1, role2';
raise info 'Message for all connected sessions.' using hint = 'all';
raise info 'Self messages will be received only by sessions that initiated the original endpoint. This is the default if not set otherwise.' using hint = 'self';
raise info 'Only for sessions with matching security context as the session that initiated the original endpoint.' using hint = 'matching';
raise info 'Only for authorized sessions.' using hint = 'authorize';
raise info 'Only for authorized sessions with roles role1 and role2.' using hint = 'authorize role1, role2';
raise info 'Message for all connected sessions.' using hint = 'all';
New Options
There are two new options to support this feature:
/// <summary>
/// Name of the request ID header that will be used to track requests. This is used to correlate requests with streaming connection ids.
/// </summary>
public string ExecutionIdHeaderName { get; set; } = "X-NpgsqlRest-ID";
/// <summary>
/// Collection of custom server-sent events response headers that will be added to the response when connected to the endpoint that is configured to return server-sent events.
/// </summary>
public Dictionary<string, StringValues> CustomServerSentEventsResponseHeaders { get; set; } = [];
ExecutionIdHeaderName
Default scope option (self) means that only sessions that initiated the call can receive these messages. To achieve that, generated client code injects a custom header on each request that has info streaming enabled with a random number value. This is the name of this header.
CustomServerSentEventsResponseHeaders
List of custom SSE (Server-Sent Events) response headers that will be added automatically. Some browsers or servers may require this to be customized.
Auth Changes
There are some slight breaking changes to how authentication and authorization works, specifically in claims handling. From this version, the library doesn't use Active Directory Federation Services Claim Types by default anymore.
The reason for this change is because Microsoft has been updating these values in newer versions which could break the authorization mechanism on updates. And since it is not really necessary, simple values are used instead.
Four different default options in AuthenticationOptions have changed values:
1. UseActiveDirectoryFederationServicesClaimTypes
From true to false obviously. When this is set to true, the value of either of these options DefaultUserIdClaimType, DefaultNameClaimType, DefaultRoleClaimType will try to match the field name (ignoring case) of this table: https://learn.microsoft.com/en-us/dotnet/api/system.security.claims.claimtypes?view=net-9.0 and will have the corresponding value.
2. DefaultUserIdClaimType
From http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier
to nameidentifier
.
3. DefaultNameClaimType
From http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name
to name
.
4. DefaultRoleClaimType
From http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role
to role
.
NpgsqlRest Client
Fix Routine Source Initialization Bug
When configuration was missing empty RoutineOptions
inside NpgsqlRest
settings, routines wouldn't initialize. The quick fix was to add an empty RoutineOptions
section like this:
{
// ...
"NpgsqlRest": {
// ...
"RoutineOptions": {
"IncludeLanguages": null
},
// ...
}
}
{
// ...
"NpgsqlRest": {
// ...
"RoutineOptions": {
"IncludeLanguages": null
},
// ...
}
}
This is no longer required.
Kestrel Configuration Improvements
Previously, Kestrel configuration allowed configuring only Endpoints
and Certificates
sections. Now it allows configuring other Kestrel options like the Limits
section and 6 other options. Full list:
{
//...
"Kestrel": {
"Endpoints": {
//...
},
"Certificates": {
//...
},
// new settings
"Limits": {
"MaxConcurrentConnections": 100,
"MaxConcurrentUpgradedConnections": 100,
"MaxRequestBodySize": 30000000,
"MaxRequestBufferSize": 1048576,
"MaxRequestHeaderCount": 100,
"MaxRequestHeadersTotalSize": 32768,
"MaxRequestLineSize": 8192,
"MaxResponseBufferSize": 65536,
"KeepAliveTimeout": "00:02:00",
"RequestHeadersTimeout": "00:00:30",
"Http2": {
"MaxStreamsPerConnection": 100,
"HeaderTableSize": 4096,
"MaxFrameSize": 16384,
"MaxRequestHeaderFieldSize": 8192,
"InitialConnectionWindowSize": 65535,
"InitialStreamWindowSize": 65535,
"MaxReadFrameSize": 16384,
"KeepAlivePingDelay": "00:00:30",
"KeepAlivePingTimeout": "00:01:00",
"KeepAlivePingPolicy": "WithActiveRequests"
},
"Http3": {
"MaxRequestHeaderFieldSize": 8192
}
},
"DisableStringReuse": false,
"AllowAlternateSchemes": false,
"AllowSynchronousIO": false,
"AllowResponseHeaderCompression": true,
"AddServerHeader": true,
"AllowHostHeaderOverride": false
},
//...
}
{
//...
"Kestrel": {
"Endpoints": {
//...
},
"Certificates": {
//...
},
// new settings
"Limits": {
"MaxConcurrentConnections": 100,
"MaxConcurrentUpgradedConnections": 100,
"MaxRequestBodySize": 30000000,
"MaxRequestBufferSize": 1048576,
"MaxRequestHeaderCount": 100,
"MaxRequestHeadersTotalSize": 32768,
"MaxRequestLineSize": 8192,
"MaxResponseBufferSize": 65536,
"KeepAliveTimeout": "00:02:00",
"RequestHeadersTimeout": "00:00:30",
"Http2": {
"MaxStreamsPerConnection": 100,
"HeaderTableSize": 4096,
"MaxFrameSize": 16384,
"MaxRequestHeaderFieldSize": 8192,
"InitialConnectionWindowSize": 65535,
"InitialStreamWindowSize": 65535,
"MaxReadFrameSize": 16384,
"KeepAlivePingDelay": "00:00:30",
"KeepAlivePingTimeout": "00:01:00",
"KeepAlivePingPolicy": "WithActiveRequests"
},
"Http3": {
"MaxRequestHeaderFieldSize": 8192
}
},
"DisableStringReuse": false,
"AllowAlternateSchemes": false,
"AllowSynchronousIO": false,
"AllowResponseHeaderCompression": true,
"AddServerHeader": true,
"AllowHostHeaderOverride": false
},
//...
}
These are just the default values.
Initial configuration is commented out so that the client uses the default versions of the framework which can change in newer versions. This opens the opportunity for Web Server optimizations like this, for example:
{
"Kestrel": {
"AddServerHeader": false, // remove unnecessary header
"Limits": {
// increase max limits for high workload
"MaxConcurrentConnections": 10000,
"MaxConcurrentUpgradedConnections": 10000
}
}
}
{
"Kestrel": {
"AddServerHeader": false, // remove unnecessary header
"Limits": {
// increase max limits for high workload
"MaxConcurrentConnections": 10000,
"MaxConcurrentUpgradedConnections": 10000
}
}
}
ThreadPool Configuration
You can also set thread pool parameters (min and max number of worker threads and completion port threads).
{
"ThreadPool": {
"MinWorkerThreads": null,
"MinCompletionPortThreads": null,
"MaxWorkerThreads": null,
"MaxCompletionPortThreads": null
}
}
{
"ThreadPool": {
"MinWorkerThreads": null,
"MinCompletionPortThreads": null,
"MaxWorkerThreads": null,
"MaxCompletionPortThreads": null
}
}
If you are expecting higher workload, you can set the initial number of minimal threads to a higher number and not wait for them to scale up automatically (which affects latency):
{
"ThreadPool": {
"MinWorkerThreads": 1000,
"MinCompletionPortThreads": 1000
}
}
{
"ThreadPool": {
"MinWorkerThreads": 1000,
"MinCompletionPortThreads": 1000
}
}
TsClient
TsClient plugin code generator for TypeScript and JavaScript clients had a major revamp to support new features.
TsClient New Settings
There are 3 new settings supported as options and as configuration settings:
ExportEventSources
Set to true to export event sources create functions for streaming events. Default is true.
CustomImports
List of custom imports to add to the generated code. It adds a line to the file. Use full expression like import { MyType } from './my-type';
. Default is an empty list.
CustomHeaders
Dictionary of custom headers to add to each request in generated code. Header key is automatically quoted if it doesn't contain quotes. Default is an empty dictionary.
TsClient Comment Annotation Parameters
TsClient now supports tweaking behavior with comment annotation parameters:
tsclient = [ false | off | disabled | disable | 0 ]
tsclient_events = [ [ false | off | disabled | disable | 0 ] | [ true | on | enabled | enable | 1 ] ]
tsclient_parse_url = [ [ false | off | disabled | disable | 0 ] | [ true | on | enabled | enable | 1 ] ]
tsclient_parse_request = [ [ false | off | disabled | disable | 0 ] | [ true | on | enabled | enable | 1 ] ]
tsclient_status_code = [ [ false | off | disabled | disable | 0 ] | [ true | on | enabled | enable | 1 ] ]
tsclient = [ false | off | disabled | disable | 0 ]
- disable tsclient code generation for the endpoint.tsclient_events = [ [ false | off | disabled | disable | 0 ] | [ true | on | enabled | enable | 1 ] ]
- enable or disable info event parameter for endpoints with info events enabled.tsclient_parse_url = [ [ false | off | disabled | disable | 0 ] | [ true | on | enabled | enable | 1 ] ]
- enable or disable info event parameter URL parsing.tsclient_parse_request = [ [ false | off | disabled | disable | 0 ] | [ true | on | enabled | enable | 1 ] ]
- enable or disable info event parameter request parsing.tsclient_status_code = [ [ false | off | disabled | disable | 0 ] | [ true | on | enabled | enable | 1 ] ]
- enable or disable status code in the return value.